/**
* The person or persons who have associated work with this document (the
* "Dedicator" or "Certifier") hereby either (a) certifies that, to the best of
* his knowledge, the work of authorship identified is in the public domain of
* the country from which the work is published, or (b) hereby dedicates
* whatever copyright the dedicators holds in the work of authorship identified
* below (the "Work") to the public domain. A certifier, moreover, dedicates any
* copyright interest he may have in the associated work, and for these
* purposes, is described as a "dedicator" below.
*
* A certifier has taken reasonable steps to verify the copyright status of this
* work. Certifier recognizes that his good faith efforts may not shield him
* from liability if in fact the work certified is not in the public domain.
*
* Dedicator makes this dedication for the benefit of the public at large and to
* the detriment of the Dedicator's heirs and successors. Dedicator intends this
* dedication to be an overt act of relinquishment in perpetuity of all present
* and future rights under copyright law, whether vested or contingent, in the
* Work. Dedicator understands that such relinquishment of all rights includes
* the relinquishment of all rights to enforce (by lawsuit or otherwise) those
* copyrights in the Work.
*
* Dedicator recognizes that, once placed in the public domain, the Work may be
* freely reproduced, distributed, transmitted, used, modified, built upon, or
* otherwise exploited by anyone for any purpose, commercial or non-commercial,
* and in any way, including by methods that have not yet been invented or
* conceived.
*/
package hudson.plugins.mysql;
import hudson.Extension;
import hudson.Util;
import hudson.model.Descriptor;
import hudson.plugins.mysql.crypt.Cipher;
import hudson.security.AbstractPasswordBasedSecurityRealm;
import hudson.security.GroupDetails;
import hudson.security.SecurityRealm;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.kohsuke.stapler.DataBoundConstructor;
import org.springframework.dao.DataAccessException;
/**
* Implementation of the AbstractPasswordBasedSecurityRealm that uses a MySQL
* database as the source of authentication information.
*
* @author Alex Ackerman
*/
public class MySQLSecurityRealm extends AbstractPasswordBasedSecurityRealm
{
@DataBoundConstructor
public MySQLSecurityRealm(String myServer, String myUsername, String myPassword,
String myPort, String myDatabase, String myDataTable, String myUserField,
String myPassField, String myCondition, String encryption)
{
this.myServer = Util.fixEmptyAndTrim(myServer);
this.myUsername = Util.fixEmptyAndTrim(myUsername);
this.myPassword = Util.fixEmptyAndTrim(myPassword);
this.myPort = Util.fixEmptyAndTrim(myPort);
if ((myPort == null) || (myPort.equals("")))
myPort = "3306";
this.myPort = myPort;
this.myDatabase = Util.fixEmptyAndTrim(myDatabase);
this.myCondition = Util.fixEmptyAndTrim(myCondition);
this.myDataTable = Util.fixEmptyAndTrim(myDataTable);
this.myUserField = Util.fixEmptyAndTrim(myUserField);
this.myPassField = Util.fixEmptyAndTrim(myPassField);
this.encryption = encryption;
}
public static final class DescriptorImpl extends Descriptor<SecurityRealm>
{
@Override
public String getHelpFile() {
return "/plugin/mysql-auth/help/overview.html";
}
@Override
public String getDisplayName() {
return Messages.DisplayName();
}
}
@Extension
public static DescriptorImpl install()
{
return new DescriptorImpl();
}
/**
* Authenticates the specified user using the password against the stored
* database configuration.
*
* @param username The username to lookup
* @param password The password to use for authentication
* @return A UserDetails object containing information about
* the user.
* @throws AuthenticationException Thrown when the username/password do
* not match stored values.
*/
@Override
protected UserDetails authenticate(String username, String password)
throws AuthenticationException
{
UserDetails userDetails = null;
String connectionString;
connectionString = "jdbc:mysql://" + myServer + "/" +
myDatabase;
LOGGER.fine("MySQLSecurity: Connection String - " + connectionString);
Connection conn = null;
try
{
// Connect to the database
Class.forName("com.mysql.jdbc.Driver").newInstance();
conn = DriverManager.getConnection(connectionString,
myUsername, myPassword);
LOGGER.info("MySQLSecurity: Connection established.");
// Prepare the statement and query the user table
// TODO: Review userQuery to see if there's a better way to do this
String userQuery = "SELECT * FROM " + myDataTable + " WHERE " +
myUserField + " = ?";
PreparedStatement statement = conn.prepareStatement(userQuery);
statement.setString(1, myDataTable);
LOGGER.fine("MySQLSecurity: Query Info - ");
LOGGER.fine("- Table: " + myDataTable);
LOGGER.fine("- User Field: " + myUserField);
LOGGER.fine("- Username: " + myUsername);
//statement.setString(2, myUserField);
statement.setString(1, username);
ResultSet results = statement.executeQuery();
LOGGER.fine("MySQLSecurity: Query executed.");
if (results.first())
{
String storedPassword = results.getString(myPassField);
Cipher cipher;
if (encryption.equals(Cipher.CRYPT))
{
String salt = storedPassword.substring(0, 2);
cipher = new Cipher(encryption, salt);
}
else
{
cipher = new Cipher(encryption);
}
String encryptedPassword = cipher.encode(password.trim());
LOGGER.fine("Encrypted Password: " + encryptedPassword);
LOGGER.fine("Stored Password: " + storedPassword);
if (!storedPassword.equals(encryptedPassword))
{
LOGGER.warning("MySQLSecurity: Invalid Username or Password");
throw new MySQLAuthenticationException("Invalid Username or Password");
}
else
{
// Password is valid. Build UserDetail
Set<GrantedAuthority> groups = new HashSet<GrantedAuthority>();
groups.add(SecurityRealm.AUTHENTICATED_AUTHORITY);
userDetails = new MySQLUserDetail(username, encryptedPassword,
true, true, true, true,
groups.toArray(new GrantedAuthority[groups.size()]));
}
}
else
{
LOGGER.warning("MySQLSecurity: Invalid Username or Password");
throw new MySQLAuthenticationException("Invalid Username or Password");
}
}
catch (Exception e)
{
LOGGER.warning("MySQLSecurity Realm Error: " + e.getLocalizedMessage());
}
finally
{
if (conn != null)
{
try
{
conn.close();
LOGGER.info("MySQLSecurity: Connection closed.");
}
catch (Exception ex)
{
/** Ignore any errors **/
}
}
}
return userDetails;
}
/**
*
* @param username
* @return
* @throws UsernameNotFoundException
* @throws DataAccessException
*/
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException
{
UserDetails user = null;
String connectionString;
connectionString = "jdbc:mysql://" + myServer + "/" +
myDatabase;
LOGGER.info("MySQLSecurity: Connection String - " + connectionString);
Connection conn = null;
try
{
// Connect to the database
Class.forName("com.mysql.jdbc.Driver").newInstance();
conn = DriverManager.getConnection(connectionString,
myUsername, myPassword);
LOGGER.info("MySQLSecurity: Connection established.");
// Prepare the statement and query the user table
// TODO: Review userQuery to see if there's a better way to do this
String userQuery = "SELECT * FROM " + myDataTable + " WHERE " +
myUserField + " = ?";
PreparedStatement statement = conn.prepareStatement(userQuery);
//statement.setString(1, myDataTable);
//statement.setString(2, myUserField);
statement.setString(1, username);
ResultSet results = statement.executeQuery();
LOGGER.fine("MySQLSecurity: Query executed.");
// Grab the first result (should be only user returned)
if (results.first())
{
// Build the user detail
Set<GrantedAuthority> groups = new HashSet<GrantedAuthority>();
groups.add(SecurityRealm.AUTHENTICATED_AUTHORITY);
user = new MySQLUserDetail(username, results.getString(myPassField),
true, true, true, true,
groups.toArray(new GrantedAuthority[groups.size()]));
}
else
{
LOGGER.warning("MySQLSecurity: Invalid Username or Password");
throw new UsernameNotFoundException("MySQL: User not found");
}
}
catch (Exception e)
{
LOGGER.warning("MySQLSecurity Realm Error: " + e.getLocalizedMessage());
}
finally
{
if (conn != null)
{
try
{
conn.close();
LOGGER.info("MySQLSecurity: Connection closed.");
}
catch (Exception ex)
{
/** Ignore any errors **/
}
}
}
return user;
}
/**
*
* @param groupname
* @return
* @throws UsernameNotFoundException
* @throws DataAccessException
*/
@Override
public GroupDetails loadGroupByGroupname(String groupname)
throws UsernameNotFoundException, DataAccessException
{
LOGGER.warning("ERROR: Group lookup is not supported.");
throw new UsernameNotFoundException("MySQLSecurityRealm: Non-supported function");
}
class Authenticator extends AbstractUserDetailsAuthenticationProvider
{
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// Assumed to be done in the retrieveUser method
}
@Override
protected UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
return MySQLSecurityRealm.this.authenticate(username,
authentication.getCredentials().toString());
}
}
public String getMyServer()
{
return myServer;
}
public String getMyUsername()
{
return myUsername;
}
public String getMyPassword()
{
return myPassword;
}
public String getMyDatabase()
{
return myDatabase;
}
public String getMyDataTable()
{
return myDataTable;
}
public String getMyUserField()
{
return myUserField;
}
public String getMyPassField()
{
return myPassField;
}
public String getMyPort()
{
return myPort;
}
public String getMyCondition()
{
return myCondition;
}
public String getEncryption()
{
return encryption;
}
/**
* Logger for debugging purposes.
*/
private static final Logger LOGGER =
Logger.getLogger(MySQLSecurityRealm.class.getName());
/**
* The MySQL server to use.
*/
private String myServer;
/**
* The MySQL username to use to connect to the database server.
*/
private String myUsername;
/**
* The MySQL password to use to connect to the database server.
*/
private String myPassword;
/**
* The database containing the user's authentication information.
*/
private String myDatabase;
/**
* The table containing a user's authentication information.
*/
private String myDataTable;
/**
* Username field in the database.
*/
private String myUserField;
/**
* Password field in the database.
*/
private String myPassField;
/**
* Port used by the MySQL server. If not specified, defaults to 3306.
*/
private String myPort;
/**
* Condition string which may prevent user from being enabled. This is a
* field used by Bugzilla.
*/
private String myCondition;
/**
* Encryption type used for the password
*/
private String encryption;
}