| /* |
| * 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.catalina.LifecycleException; |
| import org.apache.naming.ContextBindings; |
| |
| /** |
| * |
| * Implementation 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 |
| */ |
| 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; |
| |
| |
| /** |
| * 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 column in the user table that holds the user's credentials |
| */ |
| 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 |
| * @return the associated principal, or <code>null</code> if there is none. |
| */ |
| @Override |
| public Principal authenticate(String username, String credentials) { |
| |
| // No user or no credentials |
| // Can't possibly authenticate, don't bother the database then |
| if (username == null || credentials == null) { |
| return null; |
| } |
| |
| Connection dbConnection = null; |
| |
| // Ensure that we have an open database connection |
| dbConnection = open(); |
| if (dbConnection == null) { |
| // If the db connection open fails, return "not authenticated" |
| return null; |
| } |
| |
| try |
| { |
| // Acquire a Principal object for this user |
| return authenticate(dbConnection, username, credentials); |
| } |
| 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 |
| * @return the associated principal, or <code>null</code> if there is none. |
| */ |
| protected Principal authenticate(Connection dbConnection, |
| String username, |
| String credentials) { |
| // No user or no credentials |
| // Can't possibly authenticate, don't bother the database then |
| if (username == null || credentials == null) { |
| if (containerLog.isTraceEnabled()) |
| containerLog.trace(sm.getString("dataSourceRealm.authenticateFailure", |
| username)); |
| return null; |
| } |
| |
| // Look up the user's credentials |
| String dbCredentials = getPassword(dbConnection, username); |
| |
| if(dbCredentials == null) { |
| // User was not found in the database. |
| // Waste a bit of time as not to reveal that the user does not exist. |
| getCredentialHandler().mutate(credentials); |
| |
| if (containerLog.isTraceEnabled()) |
| containerLog.trace(sm.getString("dataSourceRealm.authenticateFailure", |
| username)); |
| return null; |
| } |
| |
| // Validate the user's credentials |
| boolean validated = getCredentialHandler().matches(credentials, 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<String> list = getRoles(dbConnection, username); |
| |
| // Create and return a suitable Principal for this user |
| return new GenericPrincipal(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 { |
| context = getServer().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. |
| */ |
| @Override |
| protected String getName() { |
| |
| return (name); |
| |
| } |
| |
| /** |
| * @return the password associated with the given principal's user name. |
| */ |
| @Override |
| 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 |
| * @return the password for the specified user |
| */ |
| protected String getPassword(Connection dbConnection, |
| String username) { |
| |
| String dbCredentials = null; |
| |
| try (PreparedStatement stmt = credentials(dbConnection, username); |
| ResultSet 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), e); |
| } |
| |
| return null; |
| } |
| |
| |
| /** |
| * Return the Principal associated with the given user name. |
| * @param username the user name |
| * @return the principal object |
| */ |
| @Override |
| protected Principal getPrincipal(String username) { |
| Connection dbConnection = open(); |
| if (dbConnection == null) { |
| return new GenericPrincipal(username, null, null); |
| } |
| try { |
| return (new GenericPrincipal(username, |
| getPassword(dbConnection, username), |
| getRoles(dbConnection, username))); |
| } finally { |
| close(dbConnection); |
| } |
| |
| } |
| |
| /** |
| * Return the roles associated with the given user name. |
| * @param username User name for which roles should be retrieved |
| * @return an array list of the role names |
| */ |
| protected ArrayList<String> 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 User name for which roles should be retrieved |
| * @return an array list of the role names |
| */ |
| protected ArrayList<String> getRoles(Connection dbConnection, |
| String username) { |
| |
| if (allRolesMode != AllRolesMode.STRICT_MODE && !isRoleStoreDefined()) { |
| // Using an authentication only configuration and no role store has |
| // been defined so don't spend cycles looking |
| return null; |
| } |
| |
| ArrayList<String> list = null; |
| |
| try (PreparedStatement stmt = roles(dbConnection, username); |
| ResultSet 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), e); |
| } |
| |
| 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 User name for which credentials should be retrieved |
| * @return the prepared statement |
| * @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 User name for which roles should be retrieved |
| * @return the prepared statement |
| * @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); |
| |
| } |
| |
| |
| private boolean isRoleStoreDefined() { |
| return userRoleTable != null || roleNameCol != null; |
| } |
| |
| |
| // ------------------------------------------------------ Lifecycle Methods |
| |
| /** |
| * Prepare for the beginning of active use of the public methods of this |
| * component and implement the requirements of |
| * {@link org.apache.catalina.util.LifecycleBase#startInternal()}. |
| * |
| * @exception LifecycleException if this component detects a fatal error |
| * that prevents this component from being used |
| */ |
| @Override |
| protected void startInternal() throws LifecycleException { |
| |
| // Create the roles PreparedStatement string |
| StringBuilder temp = new StringBuilder("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 StringBuilder("SELECT "); |
| temp.append(userCredCol); |
| temp.append(" FROM "); |
| temp.append(userTable); |
| temp.append(" WHERE "); |
| temp.append(userNameCol); |
| temp.append(" = ?"); |
| preparedCredentials = temp.toString(); |
| |
| super.startInternal(); |
| } |
| } |