Commit 176509c2 authored by Jesper Hvirring Henriksen's avatar Jesper Hvirring Henriksen
Browse files

Initial commit.

parents
This file indicates that the default state of this module
is enabled. To disable, create a file named disable in the
same directory as this file.
`sqlauthBcrypt:SQL`
=============
This is an authentication module for authenticating a user against a SQL database. It uses bcrypt for validation of passwords against hashed passwords stored in the database. The implementation is based heavily on sqlauth:SQL.
Options
-------
`dsn`
: The DSN which should be used to connect to the database server.
Check the various database drivers in the [PHP documentation](http://php.net/manual/en/pdo.drivers.php)
for a description of the various DSN formats.
`username`
: The username which should be used when connecting to the database server.
`password`
: The password which should be used when connecting to the database server.
If you are running this locally for development and you are using an empty
password, set this to the empty string ('').
`query`
: The SQL query which should be used to retrieve the user.
The parameters :username and :password are available.
If the username/password is incorrect, the query should return no rows.
The name of the columns in resultset will be used as attribute names.
If the query returns multiple rows, they will be merged into the attributes.
Duplicate values and NULL values will be removed.
`pepper`
: The pepper string appended to the password before generating the hash.
If you are not using a pepper, set this to the empty string ('').
`hash_column`
: The column storing password hashes.
`salt_column`
: The column storing password salts.
Examples
--------
Example - MySQL server:
'bcrypt-example' => array(
'sqlauthbcrypt:SQL',
'dsn' => 'mysql:host=sql.example.org;dbname=simplesaml',
'username' => 'userdb',
'password' => 'secretpassword',
'hash_column' => 'password_hash',
'salt_column' => 'password_salt'
'query' => 'SELECT username AS uid, name AS cn, email AS mail, password_hash, password_salt FROM users WHERE username = :username',
'pepper' => '0474f00f7823ade7d10d6797b4ceb591672c3440d92537309cedfc383a98209daf6755c043deb92936797cf74859e6924d0b395a0309950be364928188c7cf0f',
),
<?php
/**
* SQL/bcrypt authentication source
*
* This is an authentication module for authenticating a user against a SQL
* database. It uses bcrypt for validation of passwords against hashed
* passwords stored in the database. The implementation is based heavily on
* sqlauth:SQL.
*
* @author Jesper Hvirring Henriksen, Appinux A/S.
* @package simpleSAMLphp
* @version $Id$
*/
class sspmod_sqlauthBcrypt_Auth_Source_SQL extends sspmod_core_Auth_UserPassBase {
/**
* The DSN we should connect to.
*/
private $dsn;
/**
* The username we should connect to the database with.
*/
private $username;
/**
* The password we should connect to the database with.
*/
private $password;
/**
* The query we should use to retrieve the attributes for the user.
*
* The username and password will be available as :username and :password.
*/
private $query;
/**
* The pepper used to generate the password hash.
*/
private $pepper;
/**
* The column holding the password hash.
*/
private $hash_column;
/**
* The column holding the password salt.
*/
private $salt_column;
/**
* Constructor for this authentication source.
*
* @param array $info Information about this authentication source.
* @param array $config Configuration.
*/
public function __construct($info, $config) {
assert('is_array($info)');
assert('is_array($config)');
/* Call the parent constructor first, as required by the interface. */
parent::__construct($info, $config);
/* Make sure that all required parameters are present. */
foreach (array('dsn', 'username', 'password', 'query', 'pepper') as $param) {
if (!array_key_exists($param, $config)) {
throw new Exception('Missing required attribute \'' . $param .
'\' for authentication source ' . $this->authId);
}
if (!is_string($config[$param])) {
throw new Exception('Expected parameter \'' . $param .
'\' for authentication source ' . $this->authId .
' to be a string. Instead it was: ' .
var_export($config[$param], TRUE));
}
}
$this->dsn = $config['dsn'];
$this->username = $config['username'];
$this->password = $config['password'];
$this->query = $config['query'];
$this->pepper = $config['pepper'];
$this->hash_column = $config['hash_column'];
$this->salt_column = $config['salt_column'];
}
/**
* Create a database connection.
*
* @return PDO The database connection.
*/
private function connect() {
try {
$db = new PDO($this->dsn, $this->username, $this->password);
} catch (PDOException $e) {
throw new Exception('sqlauthBcrypt:' . $this->authId .
': - Failed to connect to \'' . $this->dsn . '\': '. $e->getMessage());
}
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$driver = explode(':', $this->dsn, 2);
$driver = strtolower($driver[0]);
/* Driver specific initialization. */
switch ($driver) {
case 'mysql':
/* Use UTF-8. */
$db->exec("SET NAMES 'utf8'");
break;
case 'pgsql':
/* Use UTF-8. */
$db->exec("SET NAMES 'UTF8'");
break;
}
return $db;
}
/**
* Attempt to log in using the given username and password.
*
* On a successful login, this function should return the users attributes. On failure,
* it should throw an exception. If the error was caused by the user entering the wrong
* username or password, a SimpleSAML_Error_Error('WRONGUSERPASS') should be thrown.
*
* Note that both the username and the password are UTF-8 encoded.
*
* @param string $username The username the user wrote.
* @param string $password The password the user wrote.
* @return array Associative array with the users attributes.
*/
protected function login($username, $password) {
assert('is_string($username)');
assert('is_string($password)');
$db = $this->connect();
try {
$sth = $db->prepare($this->query);
} catch (PDOException $e) {
throw new Exception('sqlauthBcrypt:' . $this->authId .
': - Failed to prepare query: ' . $e->getMessage());
}
try {
$res = $sth->execute(array('username' => $username));
} catch (PDOException $e) {
throw new Exception('sqlauthBcrypt:' . $this->authId .
': - Failed to execute query: ' . $e->getMessage());
}
try {
$data = $sth->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
throw new Exception('sqlauth:' . $this->authId .
': - Failed to fetch result set: ' . $e->getMessage());
}
SimpleSAML_Logger::info('sqlauthBcrypt:' . $this->authId .
': Got ' . count($data) . ' rows from database');
if (count($data) === 0) {
/* No rows returned - invalid username */
SimpleSAML_Logger::error('sqlauthBcrypt:' . $this->authId .
': No rows in result set. Wrong username or sqlauthBcrypt is misconfigured.');
throw new SimpleSAML_Error_Error('WRONGUSERPASS');
}
/* Validate stored password hash (must be in first row of resultset) */
$password_hash = $data[0][$this->hash_column];
$password_salt = $data[0][$this->salt_column];
if ($password_hash !== crypt($password.$this->pepper, $password_salt)) {
/* Invalid password */
SimpleSAML_Logger::error('sqlauthBcrypt:' . $this->authId .
': Hash does not match. Wrong password or sqlauthBcrypt is misconfigured.');
throw new SimpleSAML_Error_Error('WRONGUSERPASS');
}
/* Extract attributes. We allow the resultset to consist of multiple rows. Attributes
* which are present in more than one row will become multivalued. NULL values and
* duplicate values will be skipped. All values will be converted to strings.
*/
$attributes = array();
foreach ($data as $row) {
foreach ($row as $name => $value) {
if ($value === NULL) {
continue;
}
if ($name === $this->hash_column || $name === $this->salt_column) {
/* Don't add password hash and salt to attributes */
continue;
}
$value = (string)$value;
if (!array_key_exists($name, $attributes)) {
$attributes[$name] = array();
}
if (in_array($value, $attributes[$name], TRUE)) {
/* Value already exists in attribute. */
continue;
}
$attributes[$name][] = $value;
}
}
SimpleSAML_Logger::info('sqlauthBcrypt:' . $this->authId .
': Attributes: ' . implode(',', array_keys($attributes)));
return $attributes;
}
}
?>
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment