<?php /** * Simple SQL authentication source * * This class is an example authentication source which authenticates an user * against a SQL database. * * The following options are required: * It has the following options: * - dsn: The DSN which should be used to connect to the database server. Check the various * database drivers in 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. * - 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. * * Database layout used in examples: * CREATE TABLE users ( * username VARCHAR(30) NOT NULL PRIMARY KEY, * password TEXT NOT NULL, * name TEXT NOT NULL, * email TEXT NOT NULL * ); * CREATE TABLE usergroups ( * username TEXT REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE, * groupname TEXT, * UNIQUE(username, groupname) * ); * * Example - simple setup, PostgreSQL server: * 'sql-exampleorg' => array( * 'sqlauth:SQL', * 'dsn' => 'pgsql:host=sql.example.org;port=5432;dbname=simplesaml', * 'username' => 'userdb', * 'password' => 'secretpassword', * 'query' => 'SELECT username, name, email FROM users WHERE username = :username AND password = :password', * ), * * Example - multiple groups, MySQL server: * 'sql-exampleorg-groups' => array( * 'sqlauth:SQL', * 'dsn' => 'mysql:host=sql.example.org;dbname=simplesaml', * 'username' => 'userdb', * 'password' => 'secretpassword', * 'query' => 'SELECT users.username, name, email, groupname AS groups FROM users LEFT JOIN usergroups ON users.username=usergroups.username WHERE users.username = :username AND password = :password', * ), * * Example query - MD5 of salt + password, stored as salt + md5(salt + password) in password-field, MySQL server: * SELECT username, name, email * FROM users * WHERE username = :username AND SUBSTRING(password, -32) = MD5(CONCAT(SUBSTRING(password, 1, LENGTH(password) - 32), :password)) * * Example query - MD5 of salt + password, stored as salt + md5(salt + password) in password-field, PostgreSQL server: * SELECT username, name, email * FROM users * WHERE username = :username AND SUBSTRING(password FROM LENGTH(password) - 31) = MD5(SUBSTRING(password FROM 1 FOR LENGTH(password) - 32) || :password) * * @package simpleSAMLphp * @version $Id$ */ class sspmod_sqlauth_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; /** * 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') 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']; } /** * 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)'); try { $db = new PDO($this->dsn, $this->username, $this->password); } catch (PDOException $e) { throw new Exception('sqlauth:' . $this->authId . ': - Failed to connect to \'' . $this->dsn . '\': '. $e->getMessage()); } $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); try { $sth = $db->prepare($this->query); } catch (PDOException $e) { throw new Exception('sqlauth:' . $this->authId . ': - Failed to prepare query: ' . $e->getMessage()); } try { $res = $sth->execute(array('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()); } SimpleSAML_Logger::info('sqlauth:' . $this->authId . ': Got ' . count($data) . ' rows from database'); if (count($data) === 0) { /* No rows returned - invalid username/password. */ SimpleSAML_Logger::error('sqlauth:' . $this->authId . ': No rows in result set. Probably wrong username/password.'); 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; } $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('sqlauth:' . $this->authId . ': Attributes: ' . implode(',', array_keys($attributes))); return $attributes; } } ?>