| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| package org.apache.catalina.realm; |
| |
| |
| import java.security.Principal; |
| import java.sql.Connection; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.ArrayList; |
| |
| import javax.naming.Context; |
| import javax.sql.DataSource; |
| |
| import org.apache.naming.ContextBindings; |
| import org.apache.catalina.LifecycleException; |
| import org.apache.catalina.ServerFactory; |
| import org.apache.catalina.core.StandardServer; |
| import org.apache.catalina.util.StringManager; |
| |
| /** |
| * |
| * Implmentation of <b>Realm</b> that works with any JDBC JNDI DataSource. |
| * See the JDBCRealm.howto for more details on how to set up the database and |
| * for configuration options. |
| * |
| * @author Glenn L. Nielsen |
| * @author Craig R. McClanahan |
| * @author Carson McDonald |
| * @author Ignacio Ortega |
| * @version $Revision$ |
| */ |
| |
| public class DataSourceRealm |
| extends RealmBase { |
| |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| |
| /** |
| * The generated string for the roles PreparedStatement |
| */ |
| private String preparedRoles = null; |
| |
| |
| /** |
| * The generated string for the credentials PreparedStatement |
| */ |
| private String preparedCredentials = null; |
| |
| |
| /** |
| * The name of the JNDI JDBC DataSource |
| */ |
| protected String dataSourceName = null; |
| |
| |
| /** |
| * Descriptive information about this Realm implementation. |
| */ |
| protected static final String info = |
| "org.apache.catalina.realm.DataSourceRealm/1.0"; |
| |
| |
| /** |
| * Context local datasource. |
| */ |
| protected boolean localDataSource = false; |
| |
| |
| /** |
| * Descriptive information about this Realm implementation. |
| */ |
| protected static final String name = "DataSourceRealm"; |
| |
| |
| /** |
| * The column in the user role table that names a role |
| */ |
| protected String roleNameCol = null; |
| |
| |
| /** |
| * The string manager for this package. |
| */ |
| protected static final StringManager sm = |
| StringManager.getManager(Constants.Package); |
| |
| |
| /** |
| * The column in the user table that holds the user's credintials |
| */ |
| protected String userCredCol = null; |
| |
| |
| /** |
| * The column in the user table that holds the user's name |
| */ |
| protected String userNameCol = null; |
| |
| |
| /** |
| * The table that holds the relation between user's and roles |
| */ |
| protected String userRoleTable = null; |
| |
| |
| /** |
| * The table that holds user data. |
| */ |
| protected String userTable = null; |
| |
| |
| // ------------------------------------------------------------- Properties |
| |
| |
| /** |
| * Return the name of the JNDI JDBC DataSource. |
| * |
| */ |
| public String getDataSourceName() { |
| return dataSourceName; |
| } |
| |
| /** |
| * Set the name of the JNDI JDBC DataSource. |
| * |
| * @param dataSourceName the name of the JNDI JDBC DataSource |
| */ |
| public void setDataSourceName( String dataSourceName) { |
| this.dataSourceName = dataSourceName; |
| } |
| |
| /** |
| * Return if the datasource will be looked up in the webapp JNDI Context. |
| */ |
| public boolean getLocalDataSource() { |
| return localDataSource; |
| } |
| |
| /** |
| * Set to true to cause the datasource to be looked up in the webapp JNDI |
| * Context. |
| * |
| * @param localDataSource the new flag value |
| */ |
| public void setLocalDataSource(boolean localDataSource) { |
| this.localDataSource = localDataSource; |
| } |
| |
| /** |
| * Return the column in the user role table that names a role. |
| * |
| */ |
| public String getRoleNameCol() { |
| return roleNameCol; |
| } |
| |
| /** |
| * Set the column in the user role table that names a role. |
| * |
| * @param roleNameCol The column name |
| */ |
| public void setRoleNameCol( String roleNameCol ) { |
| this.roleNameCol = roleNameCol; |
| } |
| |
| /** |
| * Return the column in the user table that holds the user's credentials. |
| * |
| */ |
| public String getUserCredCol() { |
| return userCredCol; |
| } |
| |
| /** |
| * Set the column in the user table that holds the user's credentials. |
| * |
| * @param userCredCol The column name |
| */ |
| public void setUserCredCol( String userCredCol ) { |
| this.userCredCol = userCredCol; |
| } |
| |
| /** |
| * Return the column in the user table that holds the user's name. |
| * |
| */ |
| public String getUserNameCol() { |
| return userNameCol; |
| } |
| |
| /** |
| * Set the column in the user table that holds the user's name. |
| * |
| * @param userNameCol The column name |
| */ |
| public void setUserNameCol( String userNameCol ) { |
| this.userNameCol = userNameCol; |
| } |
| |
| /** |
| * Return the table that holds the relation between user's and roles. |
| * |
| */ |
| public String getUserRoleTable() { |
| return userRoleTable; |
| } |
| |
| /** |
| * Set the table that holds the relation between user's and roles. |
| * |
| * @param userRoleTable The table name |
| */ |
| public void setUserRoleTable( String userRoleTable ) { |
| this.userRoleTable = userRoleTable; |
| } |
| |
| /** |
| * Return the table that holds user data.. |
| * |
| */ |
| public String getUserTable() { |
| return userTable; |
| } |
| |
| /** |
| * Set the table that holds user data. |
| * |
| * @param userTable The table name |
| */ |
| public void setUserTable( String userTable ) { |
| this.userTable = userTable; |
| } |
| |
| |
| // --------------------------------------------------------- Public Methods |
| |
| |
| /** |
| * Return the Principal associated with the specified username and |
| * credentials, if there is one; otherwise return <code>null</code>. |
| * |
| * If there are any errors with the JDBC connection, executing |
| * the query or anything we return null (don't authenticate). This |
| * event is also logged, and the connection will be closed so that |
| * a subsequent request will automatically re-open it. |
| * |
| * @param username Username of the Principal to look up |
| * @param credentials Password or other credentials to use in |
| * authenticating this username |
| */ |
| public Principal authenticate(String username, String credentials) { |
| |
| // No user - can't possibly authenticate, don't bother the database then |
| if (username == null) { |
| return null; |
| } |
| |
| Connection dbConnection = null; |
| |
| try { |
| |
| // Ensure that we have an open database connection |
| dbConnection = open(); |
| if (dbConnection == null) { |
| // If the db connection open fails, return "not authenticated" |
| return null; |
| } |
| |
| // Acquire a Principal object for this user |
| return authenticate(dbConnection, username, credentials); |
| |
| } catch (SQLException e) { |
| // Log the problem for posterity |
| containerLog.error(sm.getString("dataSourceRealm.exception"), e); |
| |
| // Return "not authenticated" for this request |
| return (null); |
| |
| } finally { |
| close(dbConnection); |
| } |
| |
| } |
| |
| |
| // -------------------------------------------------------- Package Methods |
| |
| |
| // ------------------------------------------------------ Protected Methods |
| |
| |
| /** |
| * Return the Principal associated with the specified username and |
| * credentials, if there is one; otherwise return <code>null</code>. |
| * |
| * @param dbConnection The database connection to be used |
| * @param username Username of the Principal to look up |
| * @param credentials Password or other credentials to use in |
| * authenticating this username |
| */ |
| protected Principal authenticate(Connection dbConnection, |
| String username, |
| String credentials) throws SQLException{ |
| |
| String dbCredentials = getPassword(dbConnection, username); |
| |
| // Validate the user's credentials |
| boolean validated = false; |
| if (hasMessageDigest()) { |
| // Hex hashes should be compared case-insensitive |
| validated = (digest(credentials).equalsIgnoreCase(dbCredentials)); |
| } else |
| validated = (digest(credentials).equals(dbCredentials)); |
| |
| if (validated) { |
| if (containerLog.isTraceEnabled()) |
| containerLog.trace( |
| sm.getString("dataSourceRealm.authenticateSuccess", |
| username)); |
| } else { |
| if (containerLog.isTraceEnabled()) |
| containerLog.trace( |
| sm.getString("dataSourceRealm.authenticateFailure", |
| username)); |
| return (null); |
| } |
| |
| ArrayList list = getRoles(dbConnection, username); |
| |
| // Create and return a suitable Principal for this user |
| return (new GenericPrincipal(this, username, credentials, list)); |
| |
| } |
| |
| |
| /** |
| * Close the specified database connection. |
| * |
| * @param dbConnection The connection to be closed |
| */ |
| protected void close(Connection dbConnection) { |
| |
| // Do nothing if the database connection is already closed |
| if (dbConnection == null) |
| return; |
| |
| // Commit if not auto committed |
| try { |
| if (!dbConnection.getAutoCommit()) { |
| dbConnection.commit(); |
| } |
| } catch (SQLException e) { |
| containerLog.error("Exception committing connection before closing:", e); |
| } |
| |
| // Close this database connection, and log any errors |
| try { |
| dbConnection.close(); |
| } catch (SQLException e) { |
| containerLog.error(sm.getString("dataSourceRealm.close"), e); // Just log it here |
| } |
| |
| } |
| |
| /** |
| * Open the specified database connection. |
| * |
| * @return Connection to the database |
| */ |
| protected Connection open() { |
| |
| try { |
| Context context = null; |
| if (localDataSource) { |
| context = ContextBindings.getClassLoader(); |
| context = (Context) context.lookup("comp/env"); |
| } else { |
| StandardServer server = |
| (StandardServer) ServerFactory.getServer(); |
| context = server.getGlobalNamingContext(); |
| } |
| DataSource dataSource = (DataSource)context.lookup(dataSourceName); |
| return dataSource.getConnection(); |
| } catch (Exception e) { |
| // Log the problem for posterity |
| containerLog.error(sm.getString("dataSourceRealm.exception"), e); |
| } |
| return null; |
| } |
| |
| /** |
| * Return a short name for this Realm implementation. |
| */ |
| protected String getName() { |
| |
| return (name); |
| |
| } |
| |
| /** |
| * Return the password associated with the given principal's user name. |
| */ |
| protected String getPassword(String username) { |
| |
| Connection dbConnection = null; |
| |
| // Ensure that we have an open database connection |
| dbConnection = open(); |
| if (dbConnection == null) { |
| return null; |
| } |
| |
| try { |
| return getPassword(dbConnection, username); |
| } finally { |
| close(dbConnection); |
| } |
| } |
| |
| /** |
| * Return the password associated with the given principal's user name. |
| * @param dbConnection The database connection to be used |
| * @param username Username for which password should be retrieved |
| */ |
| protected String getPassword(Connection dbConnection, |
| String username) { |
| |
| ResultSet rs = null; |
| PreparedStatement stmt = null; |
| String dbCredentials = null; |
| |
| try { |
| stmt = credentials(dbConnection, username); |
| rs = stmt.executeQuery(); |
| if (rs.next()) { |
| dbCredentials = rs.getString(1); |
| } |
| |
| return (dbCredentials != null) ? dbCredentials.trim() : null; |
| |
| } catch(SQLException e) { |
| containerLog.error( |
| sm.getString("dataSourceRealm.getPassword.exception", |
| username)); |
| } finally { |
| try { |
| if (rs != null) { |
| rs.close(); |
| } |
| if (stmt != null) { |
| stmt.close(); |
| } |
| } catch (SQLException e) { |
| containerLog.error( |
| sm.getString("dataSourceRealm.getPassword.exception", |
| username)); |
| |
| } |
| } |
| |
| return null; |
| } |
| |
| |
| /** |
| * Return the Principal associated with the given user name. |
| */ |
| protected Principal getPrincipal(String username) { |
| Connection dbConnection = open(); |
| if (dbConnection == null) { |
| return new GenericPrincipal(this,username, null, null); |
| } |
| try { |
| return (new GenericPrincipal(this, |
| username, |
| getPassword(dbConnection, username), |
| getRoles(dbConnection, username))); |
| } finally { |
| close(dbConnection); |
| } |
| |
| } |
| |
| /** |
| * Return the roles associated with the given user name. |
| * @param username Username for which roles should be retrieved |
| */ |
| protected ArrayList getRoles(String username) { |
| |
| Connection dbConnection = null; |
| |
| // Ensure that we have an open database connection |
| dbConnection = open(); |
| if (dbConnection == null) { |
| return null; |
| } |
| |
| try { |
| return getRoles(dbConnection, username); |
| } finally { |
| close(dbConnection); |
| } |
| } |
| |
| /** |
| * Return the roles associated with the given user name |
| * @param dbConnection The database connection to be used |
| * @param username Username for which roles should be retrieved |
| */ |
| protected ArrayList getRoles(Connection dbConnection, |
| String username) { |
| |
| ResultSet rs = null; |
| PreparedStatement stmt = null; |
| ArrayList list = null; |
| |
| try { |
| stmt = roles(dbConnection, username); |
| rs = stmt.executeQuery(); |
| list = new ArrayList(); |
| |
| while (rs.next()) { |
| String role = rs.getString(1); |
| if (role != null) { |
| list.add(role.trim()); |
| } |
| } |
| return list; |
| } catch(SQLException e) { |
| containerLog.error( |
| sm.getString("dataSourceRealm.getRoles.exception", username)); |
| } |
| finally { |
| try { |
| if (rs != null) { |
| rs.close(); |
| } |
| if (stmt != null) { |
| stmt.close(); |
| } |
| } catch (SQLException e) { |
| containerLog.error( |
| sm.getString("dataSourceRealm.getRoles.exception", |
| username)); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Return a PreparedStatement configured to perform the SELECT required |
| * to retrieve user credentials for the specified username. |
| * |
| * @param dbConnection The database connection to be used |
| * @param username Username for which credentials should be retrieved |
| * |
| * @exception SQLException if a database error occurs |
| */ |
| private PreparedStatement credentials(Connection dbConnection, |
| String username) |
| throws SQLException { |
| |
| PreparedStatement credentials = |
| dbConnection.prepareStatement(preparedCredentials); |
| |
| credentials.setString(1, username); |
| return (credentials); |
| |
| } |
| |
| /** |
| * Return a PreparedStatement configured to perform the SELECT required |
| * to retrieve user roles for the specified username. |
| * |
| * @param dbConnection The database connection to be used |
| * @param username Username for which roles should be retrieved |
| * |
| * @exception SQLException if a database error occurs |
| */ |
| private PreparedStatement roles(Connection dbConnection, String username) |
| throws SQLException { |
| |
| PreparedStatement roles = |
| dbConnection.prepareStatement(preparedRoles); |
| |
| roles.setString(1, username); |
| return (roles); |
| |
| } |
| |
| // ------------------------------------------------------ Lifecycle Methods |
| |
| |
| /** |
| * |
| * Prepare for active use of the public methods of this Component. |
| * |
| * @exception LifecycleException if this component detects a fatal error |
| * that prevents it from being started |
| */ |
| public void start() throws LifecycleException { |
| |
| // Perform normal superclass initialization |
| super.start(); |
| |
| // Create the roles PreparedStatement string |
| StringBuffer temp = new StringBuffer("SELECT "); |
| temp.append(roleNameCol); |
| temp.append(" FROM "); |
| temp.append(userRoleTable); |
| temp.append(" WHERE "); |
| temp.append(userNameCol); |
| temp.append(" = ?"); |
| preparedRoles = temp.toString(); |
| |
| // Create the credentials PreparedStatement string |
| temp = new StringBuffer("SELECT "); |
| temp.append(userCredCol); |
| temp.append(" FROM "); |
| temp.append(userTable); |
| temp.append(" WHERE "); |
| temp.append(userNameCol); |
| temp.append(" = ?"); |
| preparedCredentials = temp.toString(); |
| } |
| |
| |
| /** |
| * Gracefully shut down active use of the public methods of this Component. |
| * |
| * @exception LifecycleException if this component detects a fatal error |
| * that needs to be reported |
| */ |
| public void stop() throws LifecycleException { |
| |
| // Perform normal superclass finalization |
| super.stop(); |
| |
| } |
| |
| |
| } |