0001: /*
0002: * ====================================================================
0003: * JAFFA - Java Application Framework For All
0004: *
0005: * Copyright (C) 2002 JAFFA Development Group
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2.1 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this library; if not, write to the Free Software
0019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0020: *
0021: * Redistribution and use of this software and associated documentation ("Software"),
0022: * with or without modification, are permitted provided that the following conditions are met:
0023: * 1. Redistributions of source code must retain copyright statements and notices.
0024: * Redistributions must also contain a copy of this document.
0025: * 2. Redistributions in binary form must reproduce the above copyright notice,
0026: * this list of conditions and the following disclaimer in the documentation
0027: * and/or other materials provided with the distribution.
0028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
0029: * this Software without prior written permission. For written permission,
0030: * please contact mail to: jaffagroup@yahoo.com.
0031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
0032: * appear in their names without prior written permission.
0033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
0034: *
0035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0046: * SUCH DAMAGE.
0047: * ====================================================================
0048: * The Apache Software License, Version 1.1
0049: *
0050: * Copyright (c) 1999 The Apache Software Foundation. All rights
0051: * reserved.
0052: *
0053: * Redistribution and use in source and binary forms, with or without
0054: * modification, are permitted provided that the following conditions
0055: * are met:
0056: *
0057: * 1. Redistributions of source code must retain the above copyright
0058: * notice, this list of conditions and the following disclaimer.
0059: *
0060: * 2. Redistributions in binary form must reproduce the above copyright
0061: * notice, this list of conditions and the following disclaimer in
0062: * the documentation and/or other materials provided with the
0063: * distribution.
0064: *
0065: * 3. The end-user documentation included with the redistribution, if
0066: * any, must include the following acknowlegement:
0067: * "This product includes software developed by the
0068: * Apache Software Foundation (http://www.apache.org/)."
0069: * Alternately, this acknowlegement may appear in the software itself,
0070: * if and wherever such third-party acknowlegements normally appear.
0071: *
0072: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
0073: * Foundation" must not be used to endorse or promote products derived
0074: * from this software without prior written permission. For written
0075: * permission, please contact apache@apache.org.
0076: *
0077: * 5. Products derived from this software may not be called "Apache"
0078: * nor may "Apache" appear in their names without prior written
0079: * permission of the Apache Group.
0080: *
0081: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0082: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0083: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0084: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0085: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0086: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0087: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0088: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0089: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0090: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0091: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0092: * SUCH DAMAGE.
0093: * ====================================================================
0094: *
0095: * This software consists of voluntary contributions made by many
0096: * individuals on behalf of the Apache Software Foundation. For more
0097: * information on the Apache Software Foundation, please see
0098: * <http://www.apache.org/>.
0099: *
0100: * [Additional notices, if required by prior licensing conditions]
0101: *
0102: */
0103: package org.jaffa.tomcat.realm;
0104:
0105: import java.io.File;
0106: import java.lang.reflect.InvocationTargetException;
0107: import java.lang.reflect.Method;
0108: import java.lang.reflect.Modifier;
0109: import java.security.GeneralSecurityException;
0110: import java.security.MessageDigest;
0111: import java.security.Principal;
0112: import java.sql.Connection;
0113: import java.sql.Driver;
0114: import java.sql.PreparedStatement;
0115: import java.sql.ResultSet;
0116: import java.sql.SQLException;
0117: import java.util.ArrayList;
0118: import java.util.Properties;
0119: import javax.naming.Context;
0120: import javax.naming.InitialContext;
0121: import javax.naming.NamingException;
0122: import javax.sql.DataSource;
0123: import org.apache.catalina.Container;
0124: import org.apache.catalina.Lifecycle;
0125: import org.apache.catalina.LifecycleEvent;
0126: import org.apache.catalina.LifecycleException;
0127: import org.apache.catalina.LifecycleListener;
0128: import org.apache.catalina.Logger;
0129: import org.apache.catalina.Realm;
0130: import org.apache.catalina.Server;
0131: import org.apache.catalina.ServerFactory;
0132: import org.apache.catalina.core.StandardServer;
0133: import org.apache.catalina.realm.Constants;
0134: import org.apache.catalina.realm.GenericPrincipal;
0135: import org.apache.catalina.realm.RealmBase;
0136: import org.apache.catalina.util.Base64;
0137: import org.apache.catalina.util.HexUtils;
0138: import org.apache.catalina.util.LifecycleSupport;
0139: import org.apache.catalina.util.StringManager;
0140:
0141: /**
0142: * This Realm included the functionality of both the standard tomcat
0143: * {@link org.apache.catalina.realm.JDBCRealm JDBCRealm}
0144: * and the {@link org.apache.catalina.realm.DataSourceRealm DataSourceRealm},
0145: * in one class. It also included the ability to perform custom
0146: * password encryption using 3rd party classes (if your encryption scheme is not
0147: * supported by the default MessageDigest class in the JCE libraries).
0148: * <p>
0149: * This new version works with <b>Tomcat 4.1</b> and higher, the previous version that doesn't
0150: * have the DataSource code in it, works with ther earlier Tomcat 4.0 release
0151: * <p>
0152: * The code based for this classes was taken from the Tomcat 4.1.26 release
0153: * <p>
0154: * The two properties used for encryption are : encryptionClass and encryptionMethod.
0155: * <p>
0156: * With these set, the realm introspects for the named class for the names method. It
0157: * is expected that the method has one of the following signatures
0158: * <pre>
0159: * <code>public static String methodName(String password)</code>
0160: * </pre>
0161: * or
0162: * <pre>
0163: * <code>public static String methodName(String password, String username)</code>
0164: * </pre>
0165: * The method should return a string, that should match the value retrieved from the
0166: * database. This allow one-way encryption algorithms to be used. No decryption facility
0167: * needs to be provided.
0168: * <p>
0169: * In addition to the original JDBCRealm/DataSource this realm
0170: * <ol>
0171: * <li>does not throw a NullPointerException if the database password is null
0172: * <li>considers a user validated if the database and entered password are both null
0173: * <li>provided one-way password encryption prior to comparing them
0174: * <li>allows extention to the query to get the user's credentials
0175: * <li>allows a custom query to be suppplied for getting the user's roles
0176: * <li>Message levels for 'debug' parameter are 0 ->None, 2+ ->Errors, 5+ ->Warnings, 10+ ->Debug
0177: * </ol>
0178: * New in Version 1.3
0179: * <ul>
0180: * <li>Adds support for DataSources. If the 'dataSourceName' is passed it overrides
0181: * the use of a regular connection, therefore it ignores the values for <code>connectionName,
0182: * connectionPassword, connectionURL</code> and <code>driverName</code>
0183: * <li>Uses its own LocalStrings for messages, incase you want to implement a diffenent language
0184: * </ul>
0185: * For more documentation see the <a href="http://jaffa.sourceforge.net/documentation/security/web/#realm">Jaffa Web Site</a>
0186: * <p>
0187: * @author Paul Extance
0188: * @version 1.3
0189: */
0190:
0191: public class JDBCEncryptionRealm extends RealmBase {
0192:
0193: // ----------------------------------------------------- Instance Variables
0194:
0195: /**
0196: * The name of the JNDI JDBC DataSource
0197: */
0198: protected String dataSourceName = null;
0199:
0200: /**
0201: * The connection username to use when trying to connect to the database.
0202: */
0203: protected String connectionName = null;
0204:
0205: /**
0206: * The connection URL to use when trying to connect to the database.
0207: */
0208: protected String connectionPassword = null;
0209:
0210: /**
0211: * The connection URL to use when trying to connect to the database.
0212: */
0213: protected String connectionURL = null;
0214:
0215: /**
0216: * The connection to the database.
0217: */
0218: protected Connection dbConnection = null;
0219:
0220: /**
0221: * Instance of the JDBC Driver class we use as a connection factory.
0222: */
0223: protected Driver driver = null;
0224:
0225: /**
0226: * The JDBC driver to use.
0227: */
0228: protected String driverName = null;
0229:
0230: /**
0231: * Descriptive information about this Realm implementation.
0232: */
0233: protected static final String info = "org.jaffa.tomcat.realm.JDBCEncryptionRealm/1.2";
0234:
0235: /**
0236: * Descriptive information about this Realm implementation.
0237: */
0238: protected static final String name = "JDBCEncryptionRealm";
0239:
0240: /**
0241: * The PreparedStatement to use for authenticating users.
0242: */
0243: protected PreparedStatement preparedCredentials = null;
0244:
0245: /**
0246: * The generated string for the roles PreparedStatement
0247: */
0248: private StringBuffer preparedRolesSB = null;
0249:
0250: /**
0251: * The PreparedStatement to use for identifying the roles for
0252: * a specified user.
0253: */
0254: protected PreparedStatement preparedRoles = null;
0255:
0256: /**
0257: * The generated string for the credentials PreparedStatement
0258: */
0259: private StringBuffer preparedCredentialsSB = null;
0260:
0261: /**
0262: * The column in the user role table that names a role
0263: */
0264: protected String roleNameCol = null;
0265:
0266: /**
0267: * The string manager for this package.
0268: */
0269: protected static final StringManager sm = StringManager
0270: .getManager("org.jaffa.tomcat.realm");
0271:
0272: /**
0273: * The column in the user table that holds the user's credintials
0274: */
0275: protected String userCredCol = null;
0276:
0277: /**
0278: * The column in the user table that holds the user's name
0279: */
0280: protected String userNameCol = null;
0281:
0282: /**
0283: * The table that holds the relation between user's and roles
0284: */
0285: protected String userRoleTable = null;
0286:
0287: /**
0288: * The table that holds user data.
0289: */
0290: protected String userTable = null;
0291:
0292: /** Holds value of property encryptionClass. */
0293: private String encryptionClass;
0294:
0295: /** Holds value of property encryptionMethod. */
0296: private String encryptionMethod;
0297:
0298: /** Holds value of the real encryption method to use. */
0299: private Method eMethod;
0300:
0301: /** Holds value of property roleSelect. */
0302: private String roleSelect;
0303:
0304: /** Holds value of property userClause. */
0305: private String userClause;
0306:
0307: // ------------------------------------------------------------- Properties
0308:
0309: /** Return the name of the JNDI JDBC DataSource.
0310: * @since 1.3
0311: * @return Name of the JNDI JDBC DataSource
0312: */
0313: public String getDataSourceName() {
0314: return dataSourceName;
0315: }
0316:
0317: /**
0318: * Set the name of the JNDI JDBC DataSource.
0319: *
0320: * @param dataSourceName the name of the JNDI JDBC DataSource
0321: * @since 1.3
0322: */
0323: public void setDataSourceName(String dataSourceName) {
0324: this .dataSourceName = dataSourceName;
0325: }
0326:
0327: /** Return the username to use to connect to the database.
0328: * @return Username to use to connect to the database
0329: */
0330: public String getConnectionName() {
0331: return connectionName;
0332: }
0333:
0334: /**
0335: * Set the username to use to connect to the database.
0336: *
0337: * @param connectionName Username
0338: */
0339: public void setConnectionName(String connectionName) {
0340: this .connectionName = connectionName;
0341: }
0342:
0343: /** Return the password to use to connect to the database.
0344: * @return Password to use to connect to the database
0345: */
0346: public String getConnectionPassword() {
0347: return connectionPassword;
0348: }
0349:
0350: /**
0351: * Set the password to use to connect to the database.
0352: *
0353: * @param connectionPassword User password
0354: */
0355: public void setConnectionPassword(String connectionPassword) {
0356: this .connectionPassword = connectionPassword;
0357: }
0358:
0359: /** Return the URL to use to connect to the database.
0360: * @return URL to use to connect to the database
0361: */
0362: public String getConnectionURL() {
0363: return connectionURL;
0364: }
0365:
0366: /**
0367: * Set the URL to use to connect to the database.
0368: *
0369: * @param connectionURL The new connection URL
0370: */
0371: public void setConnectionURL(String connectionURL) {
0372: this .connectionURL = connectionURL;
0373: }
0374:
0375: /** Return the JDBC driver that will be used.
0376: * @return JDBC driver that will be used
0377: */
0378: public String getDriverName() {
0379: return driverName;
0380: }
0381:
0382: /**
0383: * Set the JDBC driver that will be used.
0384: *
0385: * @param driverName The driver name
0386: */
0387: public void setDriverName(String driverName) {
0388: this .driverName = driverName;
0389: }
0390:
0391: /** Return the column in the user role table that names a role.
0392: * @return Column in the user role table that names a role
0393: */
0394: public String getRoleNameCol() {
0395: return roleNameCol;
0396: }
0397:
0398: /**
0399: * Set the column in the user role table that names a role.
0400: *
0401: * @param roleNameCol The column name
0402: */
0403: public void setRoleNameCol(String roleNameCol) {
0404: this .roleNameCol = roleNameCol;
0405: }
0406:
0407: /** Return the column in the user table that holds the user's credentials.
0408: * @return Column in the user table that holds the user's credentials
0409: */
0410: public String getUserCredCol() {
0411: return userCredCol;
0412: }
0413:
0414: /**
0415: * Set the column in the user table that holds the user's credentials.
0416: *
0417: * @param userCredCol The column name
0418: */
0419: public void setUserCredCol(String userCredCol) {
0420: this .userCredCol = userCredCol;
0421: }
0422:
0423: /** Return the column in the user table that holds the user's name.
0424: * @return Column in the user table that holds the user's name
0425: */
0426: public String getUserNameCol() {
0427: return userNameCol;
0428: }
0429:
0430: /**
0431: * Set the column in the user table that holds the user's name.
0432: *
0433: * @param userNameCol The column name
0434: */
0435: public void setUserNameCol(String userNameCol) {
0436: this .userNameCol = userNameCol;
0437: }
0438:
0439: /** Return the table that holds the relation between user's and roles.
0440: * @return Table that holds the relation between user's and roles
0441: */
0442: public String getUserRoleTable() {
0443: return userRoleTable;
0444: }
0445:
0446: /**
0447: * Set the table that holds the relation between user's and roles.
0448: *
0449: * @param userRoleTable The table name
0450: */
0451: public void setUserRoleTable(String userRoleTable) {
0452: this .userRoleTable = userRoleTable;
0453: }
0454:
0455: /** Return the table that holds user data.
0456: * @return Table that holds user data
0457: */
0458: public String getUserTable() {
0459: return userTable;
0460: }
0461:
0462: /**
0463: * Set the table that holds user data.
0464: *
0465: * @param userTable The table name
0466: */
0467: public void setUserTable(String userTable) {
0468: this .userTable = userTable;
0469: }
0470:
0471: /** Setter for property encryptionClass.
0472: * @param encryptionClass New value of property encryptionClass.
0473: */
0474: public void setEncryptionClass(String encryptionClass) {
0475: this .encryptionClass = encryptionClass;
0476: }
0477:
0478: /** Return the class used for encryption
0479: * @return Class used for encryption
0480: */
0481: public String getEncryptionClass() {
0482: return encryptionClass;
0483: }
0484:
0485: /** Setter for property encryptionMethod.
0486: * @param encryptionMethod New value of property encryptionMethod.
0487: */
0488: public void setEncryptionMethod(String encryptionMethod) {
0489: this .encryptionMethod = encryptionMethod;
0490: }
0491:
0492: /** Return the method used for encryption
0493: * @return Method used for encryption
0494: */
0495: public String getEncryptionMethod() {
0496: return encryptionMethod;
0497: }
0498:
0499: /** Setter for property roleSelect. This, if set is used as an override
0500: * for creating the complete prepared statement for retriving the list of roles
0501: * from the database.
0502: * @param roleSelect New value of property roleSelect.
0503: */
0504: public void setRoleSelect(String roleSelect) {
0505: this .roleSelect = roleSelect;
0506: }
0507:
0508: /** Return the alternative select statement for reading the roles
0509: * @return Alternative select statement for reading the roles
0510: */
0511: public String getRoleSelect() {
0512: return roleSelect;
0513: }
0514:
0515: /** Getter for property userClause.
0516: * @return Value of property userClause.
0517: */
0518: public String getUserClause() {
0519: return this .userClause;
0520: }
0521:
0522: /** Setter for property userClause.
0523: * @param userClause New value of property userClause.
0524: */
0525: public void setUserClause(String userClause) {
0526: this .userClause = userClause;
0527: }
0528:
0529: // --------------------------------------------------------- Public Methods
0530:
0531: /**
0532: * Return the Principal associated with the specified username and
0533: * credentials, if there is one; otherwise return <code>null</code>.
0534: *
0535: * If there are any errors with the JDBC connection, executing
0536: * the query or anything we return null (don't authenticate). This
0537: * event is also logged, and the connection will be closed so that
0538: * a subsequent request will automatically re-open it.
0539: *
0540: * @param username Username of the Principal to look up
0541: * @param credentials Password or other credentials to use in
0542: * authenticating this username
0543: * @return Authenticated principle object, with role access defined
0544: */
0545: public Principal authenticate(String username, String credentials) {
0546:
0547: Connection dbConnection = null;
0548:
0549: try {
0550:
0551: // Ensure that we have an open database connection
0552: dbConnection = open();
0553: if (dbConnection == null) {
0554: // If the db connection open fails, return "not authenticated"
0555: return null;
0556: }
0557:
0558: // Acquire a Principal object for this user
0559: Principal principal = null;
0560: if (dataSourceName == null)
0561: // this is syncronized (for JDBCRealm style)
0562: principal = authenticateSync(dbConnection, username,
0563: credentials);
0564: else
0565: // this is NOT syncronized (for DataSourceRealm style)
0566: principal = authenticate(dbConnection, username,
0567: credentials);
0568:
0569: // Return the Principal (if any)
0570: return (principal);
0571:
0572: } catch (SQLException e) {
0573:
0574: // Log the problem for posterity
0575: log(sm.getString("jdbcEncryptionRealm.error.exception"), e);
0576:
0577: // Close the connection so that it gets reopened next time
0578: if (dbConnection != null)
0579: close(dbConnection);
0580:
0581: // Return "not authenticated" for this request
0582: return null;
0583:
0584: } finally {
0585: // Release the database connection we just used
0586: try {
0587: release(dbConnection);
0588: dbConnection = null;
0589: } catch (SQLException e) {
0590: // do nothing
0591: }
0592: }
0593: }
0594:
0595: // -------------------------------------------------------- Package Methods
0596:
0597: // ------------------------------------------------------ Protected Methods
0598:
0599: /**
0600: * Return the Principal associated with the specified username and
0601: * credentials, if there is one; otherwise return <code>null</code>.
0602: *
0603: * This is just a syncronization guard, for use in JDBCRealm mode, in
0604: * DataSourceRealm mode, this is bypasses!
0605: *
0606: * @param dbConnection The database connection to be used
0607: * @param username Username of the Principal to look up
0608: * @param credentials Password or other credentials to use in
0609: * authenticating this username
0610: * @exception SQLException if a database error occurs
0611: * @return Authenticated principle object, with role access defined
0612: */
0613: private synchronized Principal authenticateSync(
0614: Connection dbConnection, String username, String credentials)
0615: throws SQLException {
0616: return authenticate(dbConnection, username, credentials);
0617: }
0618:
0619: /**
0620: * Return the Principal associated with the specified username and
0621: * credentials, if there is one; otherwise return <code>null</code>.
0622: *
0623: * @param dbConnection The database connection to be used
0624: * @param username Username of the Principal to look up
0625: * @param credentials Password or other credentials to use in
0626: * authenticating this username
0627: *
0628: * @exception SQLException if a database error occurs
0629: */
0630: private Principal authenticate(Connection dbConnection,
0631: String username, String credentials) throws SQLException {
0632:
0633: ResultSet rs = null;
0634: PreparedStatement stmt = null;
0635: ArrayList list = null;
0636:
0637: try {
0638: // Look up the user's credentials
0639: String dbCredentials = null;
0640: stmt = credentials(dbConnection, username);
0641: rs = stmt.executeQuery();
0642: if (rs.next()) {
0643: dbCredentials = rs.getString(1);
0644: // Fix Bug, don't trim if returned value is null!
0645: if (dbCredentials != null)
0646: dbCredentials = dbCredentials.trim();
0647: if (rs.next())
0648: log(sm.getString(
0649: "jdbcEncryptionRealm.warn.multiple",
0650: username));
0651: } else {
0652: if (debug >= 2)
0653: log(sm.getString(
0654: "jdbcEncryptionRealm.authenticateFailure",
0655: username));
0656: return (null);
0657: }
0658: rs.close();
0659: rs = null;
0660: stmt.close();
0661: stmt = null;
0662:
0663: // Validate the user's credentials
0664: boolean validated = false;
0665: if (dbCredentials == null) {
0666: // If no database password and password was entered, then validation passed
0667: validated = (credentials == null)
0668: || (credentials.trim().length() == 0);
0669: } else {
0670: // Validate the user's credentials
0671: String password = digest(encryptPassword(credentials,
0672: username));
0673: if (hasMessageDigest()) {
0674: // Hex hashes should be compared case-insensitive
0675: validated = (password
0676: .equalsIgnoreCase(dbCredentials));
0677: } else
0678: validated = (password.equals(dbCredentials));
0679: }
0680:
0681: if (validated) {
0682: if (debug >= 2)
0683: log(sm.getString(
0684: "jdbcEncryptionRealm.authenticateSuccess",
0685: username));
0686: } else {
0687: if (debug >= 2)
0688: log(sm.getString(
0689: "jdbcEncryptionRealm.authenticateFailure",
0690: username));
0691: return (null);
0692: }
0693:
0694: // Accumulate the user's roles
0695: list = new ArrayList();
0696: stmt = roles(dbConnection, username);
0697: rs = stmt.executeQuery();
0698: while (rs.next()) {
0699: list.add(rs.getString(1).trim());
0700: }
0701: } catch (GeneralSecurityException e) {
0702: // Make sure if there are any errors not to allow authnetication
0703: log(e.getMessage());
0704: return null;
0705: } finally {
0706: if (rs != null) {
0707: rs.close();
0708: }
0709: if (stmt != null) {
0710: stmt.close();
0711: }
0712: }
0713:
0714: // Create and return a suitable Principal for this user
0715: return (new GenericPrincipal(this , username, credentials, list));
0716:
0717: }
0718:
0719: /** If special encryption is needed, this is applied to the password
0720: */
0721: private String encryptPassword(String pass, String user)
0722: throws GeneralSecurityException {
0723: try {
0724: if (this .eMethod == null) {
0725: if (debug >= 10)
0726: log(sm
0727: .getString("jdbcEncryptionRealm.debug.bypass"));
0728: return pass;
0729: }
0730: if (this .eMethod.getParameterTypes().length == 1) {
0731: if (debug >= 10)
0732: log(sm.getString("jdbcEncryptionRealm.debug.type1"));
0733: return (String) this .eMethod.invoke(null,
0734: new Object[] { pass });
0735: } else if (this .eMethod.getParameterTypes().length == 2) {
0736: if (debug >= 10)
0737: log(sm.getString("jdbcEncryptionRealm.debug.type2"));
0738: return (String) this .eMethod.invoke(null, new Object[] {
0739: pass, user });
0740: } else {
0741: throw new GeneralSecurityException(sm.getString(
0742: "jdbcEncryptionRealm.badMethod",
0743: encryptionClass));
0744: }
0745: } catch (IllegalAccessException e) {
0746: throw new GeneralSecurityException(sm.getString(
0747: "jdbcEncryptionRealm.encryption", e.getMessage()));
0748: } catch (IllegalArgumentException e) {
0749: throw new GeneralSecurityException(sm.getString(
0750: "jdbcEncryptionRealm.encryption", e.getMessage()));
0751: } catch (InvocationTargetException e) {
0752: throw new GeneralSecurityException(sm.getString(
0753: "jdbcEncryptionRealm.encryption", e.getMessage()));
0754: }
0755: }
0756:
0757: /**
0758: * Close the specified database connection.
0759: *
0760: * @param dbConnection The connection to be closed
0761: */
0762: protected void close(Connection dbConnection) {
0763:
0764: // Do nothing if the database connection is already closed
0765: if (dbConnection == null)
0766: return;
0767:
0768: // Close our prepared statements (if any)
0769: try {
0770: if (preparedCredentials != null)
0771: preparedCredentials.close();
0772: } catch (Throwable f) {
0773: ;
0774: }
0775: try {
0776: if (preparedRoles != null)
0777: preparedRoles.close();
0778: } catch (Throwable f) {
0779: ;
0780: }
0781:
0782: // Close this database connection, and log any errors
0783: try {
0784: dbConnection.close();
0785: } catch (SQLException e) {
0786: log(sm.getString("jdbcEncryptionRealm.error.close"), e); // Just log it here
0787: }
0788:
0789: // Release resources associated with the closed connection
0790: this .dbConnection = null;
0791: this .preparedCredentials = null;
0792: this .preparedRoles = null;
0793:
0794: }
0795:
0796: /** Return a PreparedStatement configured to perform the SELECT required
0797: * to retrieve user credentials for the specified username.
0798: *
0799: * @param dbConnection The database connection to be used
0800: * @param username Username for which credentials should be retrieved
0801: * @exception SQLException if a database error occurs
0802: * @return PreparedStatement to read user's password
0803: */
0804: protected PreparedStatement credentials(Connection dbConnection,
0805: String username) throws SQLException {
0806:
0807: if (preparedCredentials == null) {
0808: /*
0809: StringBuffer sb = new StringBuffer("SELECT ");
0810: sb.append(userCredCol);
0811: sb.append(" FROM ");
0812: sb.append(userTable);
0813: sb.append(" WHERE ");
0814: sb.append(userNameCol);
0815: sb.append(" = ?");
0816: if(getUserClause()!=null) {
0817: sb.append(" AND ");
0818: sb.append(getUserClause());
0819: }
0820: if (debug >= 10)
0821: log("Query to read the user record is : " + sb.toString());
0822: preparedCredentials = dbConnection.prepareStatement(sb.toString());
0823: */
0824: preparedCredentials = dbConnection
0825: .prepareStatement(preparedCredentialsSB.toString());
0826: }
0827:
0828: preparedCredentials.setString(1, username);
0829: return (preparedCredentials);
0830:
0831: }
0832:
0833: /** Return a short name for this Realm implementation.
0834: * @return Short name for this Realm implementation
0835: */
0836: protected String getName() {
0837:
0838: return (this .name);
0839:
0840: }
0841:
0842: /** <B>Not Implemented</B> - Return the password associated with the given principal's user name.
0843: *
0844: * @param username User's Name
0845: * @return Returns null in all cases
0846: */
0847: protected String getPassword(String username) {
0848:
0849: return (null);
0850:
0851: }
0852:
0853: /** <B>Not Implemented</B> - Return the Principal associated with the given user name.
0854: * @param username User's Name
0855: * @return Returns null in all cases
0856: */
0857: protected Principal getPrincipal(String username) {
0858:
0859: return (null);
0860:
0861: }
0862:
0863: /** Open (if necessary) and return a database connection for use by
0864: * this Realm. Tries to use a data source if defined, otherwise open
0865: * a direct connection using the specified database driver
0866: *
0867: * @exception SQLException if a database error occurs
0868: * @return Connection to the database
0869: */
0870: protected Connection open() throws SQLException {
0871: if (dataSourceName != null) {
0872: // Get Connection From Data Source
0873: try {
0874: StandardServer server = (StandardServer) ServerFactory
0875: .getServer();
0876: Context context = server.getGlobalNamingContext();
0877: DataSource dataSource = (DataSource) context
0878: .lookup(dataSourceName);
0879: return dataSource.getConnection();
0880: } catch (Exception e) {
0881: // Log the problem for posterity
0882: log(sm.getString("jdbcEncryptionRealm.error.open"), e);
0883: return null;
0884: }
0885: } else {
0886: // Establish Connection, or return cached one...
0887:
0888: // Do nothing if there is a database connection already open
0889: if (dbConnection != null)
0890: return (dbConnection);
0891:
0892: // Instantiate our database driver if necessary
0893: if (driver == null) {
0894: try {
0895: Class clazz = Class.forName(driverName);
0896: driver = (Driver) clazz.newInstance();
0897: } catch (Throwable e) {
0898: log(sm.getString(
0899: "jdbcEncryptionRealm.error.driver", e
0900: .getMessage()), e);
0901: //log("Can't Create Database Driver Class: Reason - " + e.getMessage());
0902: throw new SQLException(e.getMessage());
0903: }
0904: }
0905:
0906: // Open a new connection
0907: Properties props = new Properties();
0908: if (connectionName != null)
0909: props.put("user", connectionName);
0910: if (connectionPassword != null)
0911: props.put("password", connectionPassword);
0912: dbConnection = driver.connect(connectionURL, props);
0913: dbConnection.setAutoCommit(false);
0914: return (dbConnection);
0915: }
0916: }
0917:
0918: /** Release our use of this connection so that it can be recycled. Only puts the connection
0919: * back in the pool if using a DataSource, otherwise the connection is held open.
0920: *
0921: * @param dbConnection Connection to release
0922: * @throws SQLException If there is an error with the database
0923: */
0924: protected void release(Connection dbConnection) throws SQLException {
0925: if (!dbConnection.getAutoCommit()) {
0926: dbConnection.commit();
0927: }
0928: // Release connection if we are using a data source.
0929: if (dataSourceName != null) {
0930: // Release the database connection we just used
0931: close(dbConnection);
0932: }
0933: }
0934:
0935: /** Return a PreparedStatement configured to perform the SELECT required
0936: * to retrieve user roles for the specified username.
0937: *
0938: * @param dbConnection The database connection to be used
0939: * @param username Username for which roles should be retrieved
0940: * @exception SQLException if a database error occurs
0941: * @return PreparedStatement to read user's roles
0942: */
0943: protected PreparedStatement roles(Connection dbConnection,
0944: String username) throws SQLException {
0945:
0946: if (preparedRoles == null) {
0947: /*
0948: if(this.roleSelect == null) {
0949: StringBuffer sb = new StringBuffer("SELECT ");
0950: sb.append(roleNameCol);
0951: sb.append(" FROM ");
0952: sb.append(userRoleTable);
0953: sb.append(" WHERE ");
0954: sb.append(userNameCol);
0955: sb.append(" = ?");
0956: preparedRoles = dbConnection.prepareStatement(sb.toString());
0957: } else
0958: preparedRoles = dbConnection.prepareStatement(this.roleSelect);
0959: */
0960: preparedRoles = dbConnection
0961: .prepareStatement(preparedRolesSB.toString());
0962: }
0963:
0964: preparedRoles.setString(1, username);
0965: return (preparedRoles);
0966:
0967: }
0968:
0969: // ------------------------------------------------------ Lifecycle Methods
0970:
0971: /**
0972: * Prepare for active use of the public methods of this Component.
0973: *
0974: * @exception LifecycleException if this component detects a fatal error
0975: * that prevents it from being started */
0976: public void start() throws LifecycleException {
0977:
0978: // Validate that we can open our connection
0979: try {
0980: Connection dbConnection = open();
0981: if (dbConnection != null)
0982: release(dbConnection);
0983: //else
0984: // throw new LifecycleException(sm.getString("jdbcEncryptionRealm.error.openNull"));
0985: } catch (SQLException e) {
0986: throw new LifecycleException(sm
0987: .getString("jdbcEncryptionRealm.error.open"), e);
0988: }
0989:
0990: // Get a reference to the Method that will be used for password Encryption
0991: if (this .encryptionClass != null) {
0992: if (this .encryptionMethod == null)
0993: throw new LifecycleException(sm.getString(
0994: "jdbcEncryptionRealm.error.param",
0995: encryptionMethod));
0996: if (debug >= 10)
0997: log(sm.getString("jdbcEncryptionRealm.debug.classOK"));
0998:
0999: // look up the method with the single input string first
1000: try {
1001: Class ec = Class.forName(this .encryptionClass);
1002: eMethod = null;
1003: try {
1004: eMethod = ec.getMethod(this .encryptionMethod,
1005: new Class[] { String.class });
1006: } catch (Exception e) {
1007: try {
1008: eMethod = ec.getMethod(this .encryptionMethod,
1009: new Class[] { String.class,
1010: String.class });
1011: } catch (Exception e2) {
1012: }
1013: }
1014:
1015: if (eMethod == null
1016: || !eMethod.getReturnType()
1017: .equals(String.class)
1018: || !Modifier.isPublic(eMethod.getModifiers())
1019: || !Modifier.isStatic(eMethod.getModifiers())) {
1020: throw new LifecycleException(sm
1021: .getString(
1022: "jdbcEncryptionRealm.error.method",
1023: this .encryptionClass,
1024: this .encryptionMethod));
1025: }
1026:
1027: } catch (ClassNotFoundException e) {
1028: throw new LifecycleException(sm.getString(
1029: "jdbcEncryptionRealm.error.class",
1030: this .encryptionClass));
1031: }
1032:
1033: if (debug >= 10)
1034: log(sm.getString("jdbcEncryptionRealm.debug.methodOK"));
1035:
1036: }
1037:
1038: // Create the roles PreparedStatement string
1039: /*
1040: preparedRolesSB = new StringBuffer("SELECT ");
1041: preparedRolesSB.append(roleNameCol);
1042: preparedRolesSB.append(" FROM ");
1043: preparedRolesSB.append(userRoleTable);
1044: preparedRolesSB.append(" WHERE ");
1045: preparedRolesSB.append(userNameCol);
1046: preparedRolesSB.append(" = ?");
1047: */
1048: if (this .roleSelect == null) {
1049: preparedRolesSB = new StringBuffer("SELECT ");
1050: preparedRolesSB.append(roleNameCol);
1051: preparedRolesSB.append(" FROM ");
1052: preparedRolesSB.append(userRoleTable);
1053: preparedRolesSB.append(" WHERE ");
1054: preparedRolesSB.append(userNameCol);
1055: preparedRolesSB.append(" = ?");
1056: } else
1057: preparedRolesSB = new StringBuffer(this .roleSelect);
1058: if (debug >= 10)
1059: log(sm.getString("jdbcEncryptionRealm.debug.roleQuery",
1060: preparedRolesSB.toString()));
1061:
1062: // Create the credentials PreparedStatement string
1063: preparedCredentialsSB = new StringBuffer("SELECT ");
1064: preparedCredentialsSB.append(userCredCol);
1065: preparedCredentialsSB.append(" FROM ");
1066: preparedCredentialsSB.append(userTable);
1067: preparedCredentialsSB.append(" WHERE ");
1068: preparedCredentialsSB.append(userNameCol);
1069: preparedCredentialsSB.append(" = ?");
1070: if (getUserClause() != null) {
1071: preparedCredentialsSB.append(" AND ");
1072: preparedCredentialsSB.append(getUserClause());
1073: }
1074: if (debug >= 10)
1075: log(sm.getString("jdbcEncryptionRealm.debug.userQuery",
1076: preparedCredentialsSB.toString()));
1077:
1078: // Perform normal superclass initialization
1079: super .start();
1080: }
1081:
1082: /** Gracefully shut down active use of the public methods of this Component.
1083: *
1084: * @exception LifecycleException if this component detects a fatal error
1085: * that needs to be reported */
1086: public void stop() throws LifecycleException {
1087:
1088: // Perform normal superclass finalization
1089: super .stop();
1090:
1091: // Close any open DB connection
1092: close(this.dbConnection);
1093:
1094: }
1095:
1096: }
|