1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
|
<?php
namespace SimpleSAML\Module\sqlauth\Auth\Source;
use Exception;
use PDO;
use PDOException;
use SimpleSAML\Error;
use SimpleSAML\Logger;
/**
* Simple SQL authentication source
*
* This class is an example authentication source which authenticates an user
* against a SQL database.
*
* @package SimpleSAMLphp
*/
class SQL extends \SimpleSAML\Module\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 options that we should connect to the database with.
*/
private $options;
/**
* 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;
/**
* 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 (['dsn', 'username', 'password', 'query'] 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'];
if (isset($config['options'])) {
$this->options = $config['options'];
}
}
/**
* Create a database connection.
*
* @return \PDO The database connection.
*/
private function connect()
{
try {
$db = new PDO($this->dsn, $this->username, $this->password, $this->options);
} catch (PDOException $e) {
// Obfuscate the password if it's part of the dsn
$obfuscated_dsn = preg_replace('/(user|password)=(.*?([;]|$))/', '${1}=***', $this->dsn);
throw new Exception('sqlauth:' . $this->authId . ': - Failed to connect to \'' .
$obfuscated_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 'utf8mb4'");
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('sqlauth:'.$this->authId.
': - Failed to prepare query: '.$e->getMessage());
}
try {
$sth->execute(['username' => $username, 'password' => $password]);
} catch (PDOException $e) {
throw new Exception('sqlauth:'.$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());
}
Logger::info('sqlauth:'.$this->authId.': Got '.count($data).
' rows from database');
if (count($data) === 0) {
// No rows returned - invalid username/password
Logger::error('sqlauth:'.$this->authId.
': No rows in result set. Probably wrong username/password.');
throw new 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 = [];
foreach ($data as $row) {
foreach ($row as $name => $value) {
if ($value === null) {
continue;
}
$value = (string) $value;
if (!array_key_exists($name, $attributes)) {
$attributes[$name] = [];
}
if (in_array($value, $attributes[$name], true)) {
// Value already exists in attribute
continue;
}
$attributes[$name][] = $value;
}
}
Logger::info('sqlauth:'.$this->authId.': Attributes: '.
implode(',', array_keys($attributes)));
return $attributes;
}
}
|