/*

* Copyright 2006 Sun Microsystems, Inc. All rights reserved.

*

* Redistribution and use in source and binary forms, with or without

* modification, are permitted provided that the following conditions

* are met:

*

* - Redistributions of source code must retain the above copyright

* notice, this list of conditions and the following disclaimer.

*
* - Redistribution in binary form must reproduce the above copyright

* notice, this list of conditions and the following disclaimer in

* the documentation and/or other materials provided with the

* distribution.

*

* Neither the name of Sun Microsystems, Inc. or the names of

* contributors may be used to endorse or promote products derived

* from this software without specific prior written permission.

*

* This software is provided "AS IS," without a warranty of any

* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND

* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,

* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY

* EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY

* DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR

* DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN

* OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA,

* OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR

* PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF

* LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE,

* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

*
* You acknowledge that Software is not designed, licensed or

* intended for use in the design, construction, operation or

* maintenance of any nuclear facility.
*/

package samples.security.jdbcrealm;
import java.util.*;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Driver;
import java.sql.DriverManager;
import javax.security.auth.login.LoginException;
import samples.security.jdbcrealm.JDBCRealm;
import com.iplanet.ias.security.auth.login.PasswordLoginModule;
import com.sun.enterprise.security.auth.AuthenticationStatus;
import com.sun.enterprise.security.auth.realm.Realm;
/**
 * JDBCRealm login module.
 *
 * <P>This login module provides a sample implementation of a custom realm.
 * You may use this sample as a template for creating alternate custom
 * authentication realm implementations to suit your applications needs.
 *
 * <P>In order to plug in a realm into the server you need to implement
 * both a login module (as shown by this class) which performs the
 * authentication and a realm (see JDBCRealm for an example) which is used
 * to manage other realm operations.
 *
 * <P>The PasswordLoginModule class is a JAAS LoginModule and must be
 * extended by this class. PasswordLoginModule provides internal
 * implementations for all the LoginModule methods (such as login(),
 * commit()). This class should not override these methods.
 *
 * <P>This class is only required to implement the authenticate() method as
 * shown below. The following rules need to be followed in the implementation
 * <P>This class is only required to implement the authenticate() method as
 * shown below. The following rules need to be followed in the implementation
 * of this method:
 *
 * <ul>

 *  <li>Your code should obtain the user and password to authenticate from
 *       _username and _password fields, respectively.
 *  <li>The authenticate method must finish with this call:
 *      return commitAuthentication(_username, _password, _currentRealm,
 *      grpList);
 *  <li>The grpList parameter is a String[] which can optionally be
 *      populated to contain the list of groups this user belongs to
 * </ul>

 *
 * <P>The PasswordLoginModule, AuthenticationStatus and other classes and
 * fields referenced in the sample code should be treated as opaque
 * undocumented interfaces.
 *
 * <P>Sample setting in server.xml for JDBCLoginModule
 * <pre>

 *    <auth-realm name="jdbc" classname="samples.security.jdbcrealm.JDBCRealm">
 *      <property name="dbdrivername" value="com.pointbase.jdbc.jdbcUniversalDriver"/>

 *      <property name="dburl"        value="jdbc:pointbase:server://localhost:9092/sample"/>
 *      <property name="dbusername"   value="public"/>

 *      <property name="dbpasswd"     value="public"/>
 *      <property name="usertable"     value="user_tbl"/>

 *      <property name="usernamecol"   value="uid"/>
 *      <property name="userpasswdcol" value="passwd"/>

 *      <property name="usergroupcol"  value="groups"/>
 *       <property name="jaas-context"  value="jdbcRealm"/>

 *    </auth-realm>
 * </pre>

 */
public class JDBCLoginModule extends PasswordLoginModule
{
    static final String PARAMS_DBDRIVERNAME= "dbdrivername";
    static final String PARAMS_DBURL       = "dburl";
    static final String PARAMS_DBUSERNAME  = "dbusername";
    static final String PARAMS_DBPASSWD    = "dbpasswd";
    static final String PARAMS_USERTABLE    = "usertable";

    static final String PARAMS_ROLETABLE    = "roletable";
    static final String PARAMS_USERNAMECOL  = "usernamecol";
    static final String PARAMS_USERPASSWDCOL= "userpasswdcol";
    static final String PARAMS_USERGROUPCOL = "usergroupcol";
    static Driver     _dbdriver = null;
    static Connection _dbConnection = null;
    /**
     * Perform authentication.
     */
    protected AuthenticationStatus authenticate()
        throws LoginException
    {
        if (!(_currentRealm instanceof JDBCRealm)) {
            throw new LoginException("JDBCLoginModule requires JDBCRealm.");
        }
        String[] grpList = this.authenticate(_username, _password);
        if (grpList == null) {  // JAAS behavior
            throw new LoginException("Failed JDBC login: " + _username);
        }
        System.out.println("JDBCRealm login succeeded.");
        return commitAuthentication(_username, _password,
                                    _currentRealm, grpList);
    }
    /**
     * Return the user group associated with the specified username and
     * credentials, if there is one; otherwise return <code>null</code>.
     *
     * @param username the user's id
     * @param passwd   the user's clear password
     */
    private String[] authenticate(String username,String passwd)
    {
        // Look up the user's credentials
        String dbCredential = null;
        String dbgroups = null;
        JDBCRealm jdbcRealm = (JDBCRealm)_currentRealm;
        String usertable    = jdbcRealm.getRealmProperty(PARAMS_USERTABLE);

        String roletable    = jdbcRealm.getRealmProperty(PARAMS_ROLETABLE);
        String usernamecol  = jdbcRealm.getRealmProperty(PARAMS_USERNAMECOL);
        String userpasswdcol= jdbcRealm.getRealmProperty(PARAMS_USERPASSWDCOL);
        String usergroupcol = jdbcRealm.getRealmProperty(PARAMS_USERGROUPCOL);
        String sql = "SELECT " + userpasswdcol + "," + usergroupcol +
               " FROM " + usertable + ","+ roletable +
               " WHERE " + usertable + "." + usernamecol + " =? and " +
               usertable + ".id = " + roletable + ".userid" ;

        PreparedStatement ps = null;
        try {
          Connection dbcon = getConnection();
          ps = dbcon.prepareStatement(sql);
          ps.setString(1, username);
          ResultSet rs = ps.executeQuery();
          if (rs.next()) {
            dbCredential = rs.getString(1).trim();
            dbgroups = rs.getString(2).trim();
          }
        } catch (SQLException e) {
          e.printStackTrace();
        } finally {
          try {
             ps.close();
          } catch (SQLException ignore) {
            ignore.printStackTrace();
          }
        }
        String[] g = null;
        if ( (dbCredential!= null) && (dbCredential.equals(passwd)) ) {
          Vector membership = new Vector();
          if (dbgroups != null) {
            StringTokenizer gst = new StringTokenizer(dbgroups,",;");
            while (gst.hasMoreTokens()) {
                membership.add(gst.nextToken() );
            }
          }
          if (membership.size()>0) {
            g = new String[membership.size()];
            membership.toArray(g);
            jdbcRealm.setGroupNames(username,g);
          }
        }
        return g;
    }
    /**
     * get jdbc connection.
     */
    private Connection getConnection() throws SQLException
    {
        if (_dbConnection != null)
            return (_dbConnection);
        if (_dbConnection != null)
            return (_dbConnection);
        JDBCRealm jdbcRealm = (JDBCRealm)_currentRealm;
        String dbdrivername = jdbcRealm.getRealmProperty(PARAMS_DBDRIVERNAME);
        String dburl        = jdbcRealm.getRealmProperty(PARAMS_DBURL);
        String dbusername   = jdbcRealm.getRealmProperty(PARAMS_DBUSERNAME);
        String dbpasswd     = jdbcRealm.getRealmProperty(PARAMS_DBPASSWD);
        if (_dbdriver == null) {
            try {
                Class clazz = Class.forName(dbdrivername);
                _dbdriver = (Driver) clazz.newInstance();
            } catch (Throwable e) {
                throw new SQLException(e.getMessage());
            }
        }
        // Open a new connection
        Properties props = new Properties();
        props.put("user",dbusername);
        props.put("password",dbpasswd);
        Connection dbcon = _dbdriver.connect(dburl,props);
        dbcon.setAutoCommit(false);
        return (dbcon);
    }
}