/**
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* https://opensso.dev.java.net/public/CDDLv1.0.html or
* opensso/legal/CDDLv1.0.txt
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at opensso/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id: JDBC.java,v 1.5 2008/08/28 21:56:45 madan_ranganath Exp $
*
*/
/**
* Portions Copyrighted 2011 ForgeRock Inc
* Portions Copyrighted 2012 Open Source Solution Technology Corporation
*/
package com.sun.identity.authentication.modules.jdbc;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.shared.datastruct.CollectionHelper;
import com.sun.identity.authentication.spi.AMLoginModule;
import com.sun.identity.authentication.spi.AuthLoginException;
import com.sun.identity.authentication.spi.InvalidPasswordException;
import com.sun.identity.authentication.util.ISAuthConstants;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Map;
import java.util.ResourceBundle;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.sql.DataSource;
public class JDBC extends AMLoginModule {
private String userTokenId;
private String userName;
private String password;
private String resultPassword;
private char[] passwordCharArray;
private java.security.Principal userPrincipal = null;
private String errorMsg = null;
private static final String amAuthJDBC = "amAuthJDBC";
private static Debug debug = Debug.getInstance(amAuthJDBC);
private ResourceBundle bundle = null;
private Map options;
private static String CONNECTIONTYPE =
ISAuthConstants.AUTH_ATTR_PREFIX_NEW + "JDBCConnectionType";
private static String JNDINAME = ISAuthConstants.AUTH_ATTR_PREFIX_NEW +
"JDBCJndiName";
private static String DRIVER = ISAuthConstants.AUTH_ATTR_PREFIX_NEW +
"JDBCDriver";
private static String URL =
ISAuthConstants.AUTH_ATTR_PREFIX_NEW + "JDBCUrl";
private static String DBUSER = ISAuthConstants.AUTH_ATTR_PREFIX_NEW +
"JDBCDbuser";
private static String DBPASSWORD = ISAuthConstants.AUTH_ATTR_PREFIX_NEW +
"JDBCDbpassword";
private static String PASSWORDCOLUMN =
ISAuthConstants.AUTH_ATTR_PREFIX_NEW + "JDBCPasswordColumn";
private static String STATEMENT = ISAuthConstants.AUTH_ATTR_PREFIX_NEW +
"JDBCStatement";
private static String TRANSFORM =ISAuthConstants.AUTH_ATTR_PREFIX_NEW +
"JDBCPasswordSyntaxTransformPlugin";
private static String AUTHLEVEL = ISAuthConstants.AUTH_ATTR_PREFIX_NEW +
"JDBCAuthLevel";
private static String DEFAULT_TRANSFORM =
"com.sun.identity.authentication.modules.jdbc.ClearTextTransform";
private String driver;
private String connectionType;
private String jndiName;
private String url;
private String dbuser;
private String dbpassword;
private String passwordColumn;
private String statement;
private String transform;
private Map sharedState;
private boolean getCredentialsFromSharedState = false;
private static final int MAX_NAME_LENGTH = 80;
private boolean useJNDI = false;
/**
* Constructor.
*/
public JDBC() {
debug.message("JDBC()");
}
/**
* Initializes parameters.
*
* @param subject
* @param sharedState
* @param options
*/
public void init(Subject subject, Map sharedState, Map options) {
debug.message("in initialize...");
java.util.Locale locale = getLoginLocale();
bundle = amCache.getResBundle(amAuthJDBC, locale);
if (debug.messageEnabled()) {
debug.message("amAuthJDBC Authentication resource bundle locale="+
locale);
}
this.options = options;
this.sharedState = sharedState;
if(options != null) {
try {
// First, figure out the type of connection
connectionType = CollectionHelper.getMapAttr(
options, CONNECTIONTYPE);
if (connectionType == null) {
debug.message("No CONNECTIONTYPE for configuring");
errorMsg ="noCONNECTIONTYPE";
return;
} else {
if (debug.messageEnabled()) {
debug.message("Found config for CONNECTIONTYPE: " +
connectionType);
}
if (connectionType.equals("JNDI")) {
useJNDI = true;
}
// If its pooled, get the JNDI name
if ( useJNDI ) {
debug.message("Using JNDI Retrieved Connection pool");
jndiName = CollectionHelper.getMapAttr(
options, JNDINAME);
if (jndiName == null) {
debug.message("No JNDINAME for configuring");
errorMsg ="noJNDINAME";
return;
} else {
if (debug.messageEnabled()) {
debug.message("Found config for JNDINAME: " +
jndiName);
}
}
// If its a non-pooled, then get the JDBC config
} else {
debug.message("Using non pooled JDBC");
driver = CollectionHelper.getMapAttr(options, DRIVER);
if (driver == null) {
debug.message("No DRIVER for configuring");
errorMsg ="noDRIVER";
return;
} else {
if (debug.messageEnabled())
debug.message("Found config for DRIVER: " +
driver);
}
url = CollectionHelper.getMapAttr(options, URL);
if (url == null) {
debug.message("No URL for configuring");
errorMsg ="noURL";
return;
} else {
if (debug.messageEnabled()) {
debug.message("Found config for URL: " + url);
}
}
dbuser = CollectionHelper.getMapAttr(options, DBUSER);
if (dbuser == null) {
debug.message("No DBUSER for configuring");
errorMsg = "noDBUSER";
return;
} else {
if (debug.messageEnabled()) {
debug.message("Found config for DBUSER: " +
dbuser);
}
}
dbpassword = CollectionHelper.getMapAttr(
options, DBPASSWORD, "");
if (dbpassword == null) {
debug.message("No DBPASSWORD for configuring");
errorMsg = "noDBPASSWORD";
return;
}
}
}
// and get the props that apply to both connection types
passwordColumn = CollectionHelper.getMapAttr(
options, PASSWORDCOLUMN);
if (passwordColumn == null) {
debug.message("No PASSWORDCOLUMN for configuring");
errorMsg = "noPASSWORDCOLUMN";
return;
} else {
if (debug.messageEnabled()) {
debug.message("Found config for PASSWORDCOLUMN: " +
passwordColumn);
}
}
statement = CollectionHelper.getMapAttr(options, STATEMENT);
if (statement == null) {
debug.message("No STATEMENT for configuring");
errorMsg = "noSTATEMENT";
}
transform = CollectionHelper.getMapAttr(options, TRANSFORM);
if (transform == null) {
if (debug.messageEnabled()) {
debug.message("No TRANSFORM for configuring."+
"Using clear text");
}
transform = DEFAULT_TRANSFORM;
} else {
if (debug.messageEnabled()) {
debug.message("Plugin for TRANSFORM: " + transform);
}
}
String authLevel = CollectionHelper.getMapAttr(
options, AUTHLEVEL);
if (authLevel != null) {
try {
setAuthLevel(Integer.parseInt(authLevel));
} catch (Exception e) {
debug.error("Unable to set auth level " +
authLevel,e);
}
}
} catch(Exception ex) {
debug.error("JDBC Init Exception", ex);
}
}
}
/**
* Processes the authentication request.
*
* @return ISAuthConstants.LOGIN_SUCCEED
as succeeded;
* ISAuthConstants.LOGIN_IGNORE
as failed.
* @exception AuthLoginException upon any failure. login state should be
* kept on exceptions for status check in auth chaining.
*/
public int process(Callback[] callbacks, int state)
throws AuthLoginException {
// return if this module is already done
if (errorMsg != null) {
throw new AuthLoginException(amAuthJDBC, errorMsg, null);
}
if (debug.messageEnabled()) {
debug.message("State: " + state);
}
if (state != ISAuthConstants.LOGIN_START) {
throw new AuthLoginException(amAuthJDBC, "invalidState", null);
}
if (callbacks != null && callbacks.length == 0) {
userName = (String) sharedState.get(getUserKey());
password = (String) sharedState.get(getPwdKey());
if (userName == null || password == null) {
return ISAuthConstants.LOGIN_START;
}
getCredentialsFromSharedState = true;
} else {
userName = ((NameCallback) callbacks[0]).getName();
if (debug.messageEnabled()) {
debug.message("Authenticating this user: " + userName);
}
passwordCharArray = ((PasswordCallback) callbacks[1]).getPassword();
password = new String(passwordCharArray);
if (userName == null || userName.length() == 0) {
throw new AuthLoginException(amAuthJDBC, "noUserName", null);
}
}
storeUsernamePasswd(userName, password);
// Check if they'return being a bit malicious with their UID.
// SQL attacks will be handled by prepared stmt escaping.
if (userName.length() > MAX_NAME_LENGTH ) {
throw new AuthLoginException(amAuthJDBC, "userNameTooLong", null);
}
Connection database = null;
PreparedStatement thisStatement = null;
ResultSet results = null;
try {
if ( useJNDI ) {
Context initctx = new InitialContext();
DataSource ds = (DataSource)initctx.lookup(jndiName);
if (debug.messageEnabled()) {
debug.message("Datasource Acquired: " + ds.toString());
}
database = ds.getConnection();
debug.message("Using JNDI Retrieved Connection pool");
} else {
Class.forName (driver);
database = DriverManager.getConnection(url,dbuser,dbpassword);
}
if (debug.messageEnabled()) {
debug.message("Connection Acquired: " + database.toString());
}
//Prepare the statement for execution
if (debug.messageEnabled()) {
debug.message("PreparedStatement to build: " + statement);
}
thisStatement =
database.prepareStatement(statement);
thisStatement.setString(1,userName);
if (debug.messageEnabled()) {
debug.message("Statement to execute: " + thisStatement);
}
// execute the query
results = thisStatement.executeQuery();
if (results == null) {
debug.message("returned null from executeQuery()");
throw new AuthLoginException(amAuthJDBC, "nullResult", null);
}
//parse the results. should only be one item in one row.
int index = 0;
while (results.next()) {
// do normal processing..its the first and last row
index ++;
if (index > 1) {
if (debug.messageEnabled()) {
debug.message("Too many results."+
"UID should be a primary key");
}
throw new AuthLoginException(amAuthJDBC, "multiEntry",null);
}
resultPassword = results.getString(passwordColumn).trim();
}
if (index == 0) {
// no results
if (debug.messageEnabled()) {
debug.message("No results from your SQL query."+
"UID should be valid");
}
throw new AuthLoginException(amAuthJDBC, "nullResult", null);
}
} catch (Throwable e) {
if (getCredentialsFromSharedState && !isUseFirstPassEnabled()) {
getCredentialsFromSharedState = false;
return ISAuthConstants.LOGIN_START;
}
if (debug.messageEnabled()) {
debug.message("JDBC Exception:", e);
}
throw new AuthLoginException(e);
} finally {
// close the resultset
if (results != null) {
try {
results.close();
} catch (Exception e) {
// ignore
}
}
// close the statement
if (thisStatement != null) {
try {
thisStatement.close();
} catch (Exception e) {
// ignore
}
}
// close the connection when done
if (database != null) {
try {
database.close();
} catch (Exception dbe) {
debug.error("Error in closing database connection: " +
dbe.getMessage());
if (debug.messageEnabled()) {
debug.message("Fail to close database:", dbe);
}
}
}
}
if (!transform.equals(DEFAULT_TRANSFORM)) {
try {
JDBCPasswordSyntaxTransform syntaxTransform =
(JDBCPasswordSyntaxTransform)Class.forName(transform)
.newInstance();
if (debug.messageEnabled()) {
debug.message("Got my Transform Object" +
syntaxTransform.toString() );
}
password = syntaxTransform.transform(password);
if (debug.messageEnabled()) {
debug.message("Password transformed by: " + transform );
}
} catch (Throwable e) {
if (debug.messageEnabled()) {
debug.message("Syntax Transform Exception:"+ e.toString());
}
throw new AuthLoginException(e);
}
}
// see if the passwords match
if (password != null && password.equals(resultPassword)) {
userTokenId = userName;
return ISAuthConstants.LOGIN_SUCCEED;
} else {
debug.message("password not match. Auth failed.");
setFailureID(userName);
throw new InvalidPasswordException(amAuthJDBC, "loginFailed",
null, userName, null);
}
}
/**
* Returns principal of the authenticated user.
*
* @return Principal of the authenticated user.
*/
public java.security.Principal getPrincipal() {
if (userPrincipal != null) {
return userPrincipal;
} else if (userTokenId != null) {
userPrincipal = new JDBCPrincipal(userTokenId);
return userPrincipal;
} else {
return null;
}
}
/**
* Cleans up the login state.
*/
public void destroyModuleState() {
userTokenId = null;
userPrincipal = null;
}
public void nullifyUserdVars() {
userName = null;
password = null;
resultPassword = null;
passwordCharArray = null;
errorMsg = null;
bundle = null;
options = null;
driver = null;
connectionType = null;
jndiName = null;
url = null;
dbuser = null;
dbpassword = null;
passwordColumn = null;
statement = null;
transform = null;
sharedState = null;
}
}