JDBCAuthProvider.java 11.2 KB
Newer Older
1 2 3 4
/**
 * $Revision: 1116 $
 * $Date: 2005-03-10 20:18:08 -0300 (Thu, 10 Mar 2005) $
 *
5
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
6 7
 *
 * This software is published under the terms of the GNU Public License (GPL),
8 9
 * a copy of which is included in this distribution, or a commercial license
 * agreement with Jive.
10 11
 */

12
package org.jivesoftware.openfire.auth;
13

14 15 16 17 18 19
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.JiveGlobals;
20 21 22
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;

23
import java.sql.*;
24 25 26 27 28 29

/**
 * 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>
30
 *
31 32 33 34
 * To enable this provider, set the following in the system properties:
 * <ul>
 * <li><tt>provider.auth.className = org.jivesoftware.openfire.auth.JDBCAuthProvider</tt></li>
 * </ul>
35
 *
36
 * You'll also need to set your JDBC driver, connection string, and SQL statements:
37
 *
38 39 40 41 42 43
 * <ul>
 * <li><tt>jdbcProvider.driver = com.mysql.jdbc.Driver</tt></li>
 * <li><tt>jdbcProvider.connectionString = jdbc:mysql://localhost/dbname?user=username&amp;password=secret</tt></li>
 * <li><tt>jdbcAuthProvider.passwordSQL = SELECT password FROM user_account WHERE username=?</tt></li>
 * <li><tt>jdbcAuthProvider.passwordType = plain</tt></li>
 * </ul>
44
 *
45
 * The passwordType setting tells Openfire how the password is stored. Setting the value
46 47 48 49 50
 * 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>
51 52 53 54 55
 *
 * @author David Snopek
 */
public class JDBCAuthProvider implements AuthProvider {

56 57
    private String connectionString;

58
    private String passwordSQL;
59
    private PasswordType passwordType;
60 61 62 63 64

    /**
     * Constructs a new JDBC authentication provider.
     */
    public JDBCAuthProvider() {
65 66 67 68 69 70
        // Convert XML based provider setup to Database based
        JiveGlobals.migrateProperty("jdbcProvider.driver");
        JiveGlobals.migrateProperty("jdbcProvider.connectionString");
        JiveGlobals.migrateProperty("jdbcAuthProvider.passwordSQL");
        JiveGlobals.migrateProperty("jdbcAuthProvider.passwordType");

71
        // Load the JDBC driver and connection string.
72
        String jdbcDriver = JiveGlobals.getProperty("jdbcProvider.driver");
73 74 75 76 77 78 79
        try {
            Class.forName(jdbcDriver).newInstance();
        }
        catch (Exception e) {
            Log.error("Unable to load JDBC driver: " + jdbcDriver, e);
            return;
        }
80
        connectionString = JiveGlobals.getProperty("jdbcProvider.connectionString");
81

82
        // Load SQL statements.
83
        passwordSQL = JiveGlobals.getProperty("jdbcAuthProvider.passwordSQL");
84 85
        passwordType = PasswordType.plain;
        try {
86
            passwordType = PasswordType.valueOf(
87
                    JiveGlobals.getProperty("jdbcAuthProvider.passwordType", "plain"));
88 89 90 91
        }
        catch (IllegalArgumentException iae) {
            Log.error(iae);
        }
92 93 94 95 96 97 98
    }

    public void authenticate(String username, String password) throws UnauthorizedException {
        if (username == null || password == null) {
            throw new UnauthorizedException();
        }
        username = username.trim().toLowerCase();
99 100 101 102
        if (username.contains("@")) {
            // Check that the specified domain matches the server's domain
            int index = username.indexOf("@");
            String domain = username.substring(index + 1);
103
            if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())) {
104 105 106 107 108 109
                username = username.substring(0, index);
            } else {
                // Unknown domain. Return authentication failed.
                throw new UnauthorizedException();
            }
        }
110 111
        String userPassword;
        try {
112
            userPassword = getPasswordValue(username);
113 114 115 116
        }
        catch (UserNotFoundException unfe) {
            throw new UnauthorizedException();
        }
117 118 119 120
        // If the user's password doesn't match the password passed in, authentication
        // should fail.
        if (passwordType == PasswordType.md5) {
            password = StringUtils.hash(password, "MD5");
121
        }
122 123
        else if (passwordType == PasswordType.sha1) {
            password = StringUtils.hash(password, "SHA-1");
124
        }
125 126
        if (!password.equals(userPassword)) {
            throw new UnauthorizedException();
127 128 129 130 131 132 133
        }

        // Got this far, so the user must be authorized.
        createUser(username);
    }

    public void authenticate(String username, String token, String digest)
134 135 136 137 138 139
            throws UnauthorizedException
    {
        if (passwordType != PasswordType.plain) {
            throw new UnsupportedOperationException("Digest authentication not supported for "
                    + "password type " + passwordType);
        }
140 141 142 143
        if (username == null || token == null || digest == null) {
            throw new UnauthorizedException();
        }
        username = username.trim().toLowerCase();
144 145 146 147
        if (username.contains("@")) {
            // Check that the specified domain matches the server's domain
            int index = username.indexOf("@");
            String domain = username.substring(index + 1);
148
            if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())) {
149 150 151 152 153 154
                username = username.substring(0, index);
            } else {
                // Unknown domain. Return authentication failed.
                throw new UnauthorizedException();
            }
        }
155 156
        String password;
        try {
157
            password = getPasswordValue(username);
158 159 160 161
        }
        catch (UserNotFoundException unfe) {
            throw new UnauthorizedException();
        }
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
        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);
    }

181 182 183
    public String getPassword(String username) throws UserNotFoundException,
            UnsupportedOperationException
    {
184

185 186 187
        if (!supportsPasswordRetrieval()) {
            throw new UnsupportedOperationException();
        }
188 189 190 191
        if (username.contains("@")) {
            // Check that the specified domain matches the server's domain
            int index = username.indexOf("@");
            String domain = username.substring(index + 1);
192
            if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())) {
193
                username = username.substring(0, index);
194 195 196
            } else {
                // Unknown domain.
                throw new UserNotFoundException();
197 198
            }
        }
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
        return getPasswordValue(username);
    }

    public void setPassword(String username, String password)
            throws UserNotFoundException, UnsupportedOperationException
    {
        throw new UnsupportedOperationException();
    }

    public boolean supportsPasswordRetrieval() {
        return (passwordSQL != null && passwordType == PasswordType.plain);
    }

    /**
     * Returns the value of the password field. It will be in plain text or hashed
     * format, depending on the password type.
     *
216
     * @param username user to retrieve the password field for
217 218 219 220
     * @return the password value.
     * @throws UserNotFoundException if the given user could not be loaded.
     */
    private String getPasswordValue(String username) throws UserNotFoundException {
221 222
        String password = null;
        Connection con = null;
223
        PreparedStatement pstmt = null;
224
        ResultSet rs = null;
225 226 227 228
        if (username.contains("@")) {
            // Check that the specified domain matches the server's domain
            int index = username.indexOf("@");
            String domain = username.substring(index + 1);
229
            if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())) {
230 231 232 233 234 235
                username = username.substring(0, index);
            } else {
                // Unknown domain.
                throw new UserNotFoundException();
            }
        }
236
        try {
237
            con = DriverManager.getConnection(connectionString);
238
            pstmt = con.prepareStatement(passwordSQL);
239 240
            pstmt.setString(1, username);

241
            rs = pstmt.executeQuery();
242 243 244 245

            // If the query had no results, the username and password
            // did not match a user record. Therefore, throw an exception.
            if (!rs.next()) {
246
                throw new UserNotFoundException();
247
            }
248
            password = rs.getString(1);
249 250 251
        }
        catch (SQLException e) {
            Log.error("Exception in JDBCAuthProvider", e);
252
            throw new UserNotFoundException();
253 254
        }
        finally {
255
            DbConnectionManager.closeConnection(rs, pstmt, con);
256
        }
257
        return password;
258 259
    }

260 261 262
    /**
     * Indicates how the password is stored.
     */
263
    @SuppressWarnings({"UnnecessarySemicolon"})  // Support for QDox Parser
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
    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.
         */
279
        sha1;
280 281 282 283 284 285 286
    }

    /**
     * Checks to see if the user exists; if not, a new user is created.
     *
     * @param username the username.
     */
287 288 289 290 291 292 293 294
    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 {
295
                Log.debug("JDBCAuthProvider: Automatically creating new user account for " + username);
296 297 298 299 300 301 302 303 304
                UserManager.getUserProvider().createUser(username, StringUtils.randomString(8),
                        null, null);
            }
            catch (UserAlreadyExistsException uaee) {
                // Ignore.
            }
        }
    }
}