/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.security;
import java.security.Principal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import com.caucho.config.ConfigException;
import com.caucho.config.types.Period;
import com.caucho.server.dispatch.ServletConfigException;
import com.caucho.server.security.CachingPrincipal;
import com.caucho.util.L10N;
/**
* An authenticator using JDBC.
*
* <p>The default table schema looks something like:
* <pre>
* CREATE TABLE LOGIN (
* username VARCHAR(250) NOT NULL,
* password VARCHAR(250),
* cookie VARCHAR(250),
* PRIMARY KEY (username)
* );
* </pre>
*
* <code><pre>
* <security:DatabaseAuthenticator data-source="jdbc/user"/>
* </pre></code>
*/
@SuppressWarnings("serial")
public class DatabaseAuthenticator extends AbstractCookieAuthenticator {
private static final Logger log
= Logger.getLogger(DatabaseAuthenticator.class.getName());
private static final L10N L = new L10N(DatabaseAuthenticator.class);
private DataSource _dataSource;
private String _passwordQuery = "SELECT password FROM LOGIN WHERE username=?";
private String _cookieUpdate = "UPDATE LOGIN SET cookie=? WHERE username=?";
private String _cookieQuery = "SELECT username FROM LOGIN where cookie=?";
private boolean _cookieLogout;
private String _roleQuery;
protected boolean _useCookie;
private String _authCookieName = "resinauthid";
protected int _cookieVersion = -1;
protected String _cookieDomain;
protected long _cookieMaxAge = 365L * 24L * 3600L * 1000L;
public DatabaseAuthenticator()
{
}
/**
* Gets the database
*/
public DataSource getDataSource()
{
return _dataSource;
}
/**
* Sets the database pool name.
*/
public void setDataSource(DataSource dataSource)
{
_dataSource = dataSource;
}
/**
* Gets the password query.
*
* <p>Example:
* <pre><code>
* SELECT password FROM LOGIN WHERE username=?
* </code></pre>
*/
public String getPasswordQuery()
{
return _passwordQuery;
}
/**
* Sets the password query.
*/
public void setPasswordQuery(String query)
{
_passwordQuery = query;
}
/**
* Gets the cookie auth query.
*/
public String getCookieAuthQuery()
{
return _cookieQuery;
}
/**
* Sets the cookie auth query.
*/
public void setCookieAuthQuery(String query)
{
_cookieQuery = query;
}
/**
* Gets the cookie update query.
*/
public String getCookieAuthUpdate()
{
return _cookieUpdate;
}
/**
* Sets the cookie update query.
*/
public void setCookieAuthUpdate(String query)
{
_cookieUpdate = query;
}
/**
* If true, the cookie is removed on logout
*/
public void setCookieLogout(boolean cookieLogout)
{
_cookieLogout = cookieLogout;
}
/**
* Gets the role query.
*/
public String getRoleQuery()
{
return _roleQuery;
}
/**
* Sets the role query.
*/
public void setRoleQuery(String query)
{
_roleQuery = query;
}
/**
* Returns true if Resin should generate the resinauth cookie by default.
*/
public boolean getUseCookie()
{
return _useCookie;
}
/**
* Set true if Resin should generate the resinauth cookie by default.
*/
public void setUseCookie(boolean useCookie)
{
_useCookie = useCookie;
}
/**
* Returns the version for a login cookie.
*/
public int getCookieVersion()
{
return _cookieVersion;
}
/**
* Sets the version for a login cookie.
*/
public void setCookieVersion(int version)
{
_cookieVersion = version;
}
/**
* Returns the domain for a login cookie.
*/
public String getCookieDomain()
{
return _cookieDomain;
}
/**
* Sets the domain for a login cookie.
*/
public void setCookieDomain(String cookieDomain)
{
_cookieDomain = cookieDomain;
}
/**
* Returns the max-age for a login cookie.
*/
public long getCookieMaxAge()
{
return _cookieMaxAge;
}
/**
* Sets the max age for a login cookie.
*/
public void setCookieMaxAge(Period cookieMaxAge)
{
_cookieMaxAge = cookieMaxAge.getPeriod();
}
/**
* Initialize the authenticator.
*/
@PostConstruct
public void init()
throws ServletException
{
super.init();
if (_dataSource == null)
throw new ConfigException(L.l("JdbcAuthenticator requires a 'data-source' attribute, because it depends on a JDBC database"));
int i = _passwordQuery.indexOf('?');
if (i < 0)
throw new ConfigException(L.l("'password-query' expects a parameter"));
if (_cookieQuery != null) {
i = _cookieQuery.indexOf('?');
if (i < 0)
throw new ConfigException(L.l("'cookie-auth-query' expects a parameter"));
}
if (_cookieUpdate != null) {
i = _cookieUpdate.indexOf('?');
if (i < 0)
throw new ConfigException(L.l("'cookie-auth-update' expects two parameters for the cookie id and the user id."));
int j = _cookieUpdate.indexOf('?', i + 1);
if (j < 0)
throw new ConfigException(L.l("'cookie-auth-update' expects two parameters"));
}
if (_cookieUpdate != null && _cookieQuery == null)
throw new ServletConfigException(L.l("<cookie-auth-update> expects 'cookie-query' because both update and select queries are needed."));
if (_roleQuery != null) {
i = _roleQuery.indexOf('?');
if (i < 0)
throw new ConfigException(L.l("'role-query' expects a parameter"));
}
}
/**
* Main authenticator API.
*/
protected Principal authenticate(Principal principal,
PasswordCredentials cred,
Object details)
{
char []password = cred.getPassword();
char []digest = getPasswordDigest(principal.getName(), password);
return authenticate(principal.getName(),
new String(digest),
(HttpServletRequest) details);
}
/**
* Authenticates the user given the request.
*
* @param username the user name for the login
* @param password the password for the login
*
* @return the authenticated user or null for a failure
*/
public Principal authenticate(String username,
String password,
HttpServletRequest request)
{
Principal user = loginImpl(username, password);
if (_cookieQuery == null || user == null)
return user;
String cookieAuth = (String) request.getAttribute("j_use_cookie_auth");
if (cookieAuth == null)
cookieAuth = (String) request.getParameter("j_use_cookie_auth");
if ("true".equals(cookieAuth) || "on".equals(cookieAuth)
|| _useCookie && cookieAuth == null) {
addAuthCookie(user, request);
}
return user;
}
/**
* Returns the authentication cookie
*/
@Override
public boolean isCookieSupported(String jUseCookieAuth)
{
if (_cookieQuery == null)
return false;
else if ("false".equals(jUseCookieAuth) || "off".equals(jUseCookieAuth))
return false;
else if (_useCookie)
return true;
else
return ("true".equals(jUseCookieAuth) || "on".equals(jUseCookieAuth));
}
/**
* Adds a cookie to store authentication.
*/
protected void addAuthCookie(Principal user,
HttpServletRequest request)
{
/*
Application app = (Application) application;
SessionManager sm = app.getSessionManager();
String id;
id = sm.createSessionId(request);
if (updateCookie(user, id)) {
Cookie cookie = new Cookie("resinauthid", id);
cookie.setPath("/");
if (getCookieVersion() >= 0)
cookie.setVersion(getCookieVersion());
else
cookie.setVersion(sm.getCookieVersion());
if (_cookieDomain != null)
cookie.setDomain(_cookieDomain);
else if (getCookieDomain() != null)
cookie.setDomain(getCookieDomain());
else
cookie.setDomain(sm.getCookieDomain());
if (_cookieMaxAge > 0)
cookie.setMaxAge((int) (_cookieMaxAge / 1000L));
response.addCookie(cookie);
}
*/
}
/**
* Authenticates the user given the request.
*
* @param username the user name for the login
* @param password the password for the login
*
* @return the authenticated user or null for a failure
*/
public Principal loginImpl(String username, String password)
{
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = _dataSource.getConnection();
stmt = conn.prepareStatement(_passwordQuery);
stmt.setString(1, username);
rs = stmt.executeQuery();
if (! rs.next()) {
if (log.isLoggable(Level.FINE))
log.fine("no such user:" + username);
return null;
}
String dbPassword = rs.getString(1);
if (dbPassword != null && dbPassword.equals(password)) {
return new CachingPrincipal(username);
}
else {
if (log.isLoggable(Level.FINE))
log.fine("mismatched password:" + username);
return null;
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
try {
if (rs != null)
rs.close();
} catch (SQLException e) {
}
try {
if (stmt != null)
stmt.close();
} catch (SQLException e) {
}
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
}
}
}
/**
* Returns the password for authenticators too lazy to calculate the
* digest.
*/
@Override
protected PasswordUser getPasswordUser(String username)
{
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = _dataSource.getConnection();
stmt = conn.prepareStatement(_passwordQuery);
stmt.setString(1, username);
rs = stmt.executeQuery();
if (! rs.next()) {
if (log.isLoggable(Level.FINE))
log.fine("no such user:" + username);
return null;
}
String dbPassword = rs.getString(1);
return new PasswordUser(new BasicPrincipal(username),
dbPassword.toCharArray(),
false,
false,
new String[0]);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (rs != null)
rs.close();
} catch (SQLException e) {
}
try {
if (stmt != null)
stmt.close();
} catch (SQLException e) {
}
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
}
}
}
/**
* Authenticate based on a cookie.
*
* @param cookieValue the value of the resin-auth cookie
*
* @return the user for the cookie.
*/
public Principal authenticateByCookie(String cookieValue)
{
if (_cookieQuery == null)
return null;
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = _dataSource.getConnection();
stmt = conn.prepareStatement(_cookieQuery);
stmt.setString(1, cookieValue);
rs = stmt.executeQuery();
if (! rs.next())
return null;
String user = rs.getString(1);
if (user != null)
return new CachingPrincipal(user);
else
return null;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (rs != null)
rs.close();
} catch (SQLException e) {
}
try {
if (stmt != null)
stmt.close();
} catch (SQLException e) {
}
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
}
}
}
/**
* Associates a user with a persistent cookie.
*
* @param user the user for the cookie
* @param cookieValue the value of the resin-auth cookie
*
* @return true if the cookie value is valid, i.e. it's unique
*/
public boolean associateCookie(Principal user, String cookieValue)
{
if (_cookieUpdate == null || user == null || cookieValue == null)
return true;
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = _dataSource.getConnection();
stmt = conn.prepareStatement(_cookieUpdate);
stmt.setString(1, cookieValue);
stmt.setString(2, user.getName());
stmt.executeUpdate();
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
} finally {
try {
if (stmt != null)
stmt.close();
} catch (SQLException e) {
}
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
}
}
return true;
}
@Override
public boolean isUserInRole(Principal principal, String role)
{
if (_roleQuery == null)
return principal != null && "user".equals(role);
else if (principal == null || role == null)
return false;
CachingPrincipal cachingPrincipal = null;
if (principal instanceof CachingPrincipal) {
cachingPrincipal = (CachingPrincipal) principal;
Boolean isInRole = cachingPrincipal.isInRole(role);
if (isInRole != null)
return isInRole.equals(Boolean.TRUE);
}
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = _dataSource.getConnection();
stmt = conn.prepareStatement(_roleQuery);
stmt.setString(1, principal.getName());
boolean inRole = false;
rs = stmt.executeQuery();
while (rs.next()) {
String dbRole = rs.getString(1);
if (cachingPrincipal != null)
cachingPrincipal.addRole(dbRole);
if (role.equals(dbRole))
inRole = true;
}
return inRole;
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
return false;
} finally {
try {
if (rs != null)
rs.close();
} catch (SQLException e) {
}
try {
if (stmt != null)
stmt.close();
} catch (SQLException e) {
}
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
}
}
}
class JdbcUser implements AuthenticatedUser {
private Principal _user;
JdbcUser(Principal user)
{
_user = user;
}
public String getName()
{
return _user.getName();
}
public Principal getPrincipal()
{
return _user;
}
public boolean isUserInRole(String role)
{
return DatabaseAuthenticator.this.isUserInRole(_user, role);
}
public void logout()
{
}
public String toString()
{
return getClass().getSimpleName() + "[" + getName() + "]";
}
}
}