From e1ad2dbda1ce3c196e63f32691db959bac57d3c8 Mon Sep 17 00:00:00 2001 From: Tyler Antonio <contact@tanton.io> Date: Tue, 16 Jun 2015 11:42:15 -0600 Subject: [PATCH] Created a central database class and updated the PDO MetaDataStorangeHandler to use it instead of its own database classes --- config-templates/config.php | 67 +++++- lib/SimpleSAML/Database.php | 202 ++++++++++++++++++ .../Metadata/MetaDataStorageHandlerPdo.php | 48 ++--- 3 files changed, 280 insertions(+), 37 deletions(-) create mode 100644 lib/SimpleSAML/Database.php diff --git a/config-templates/config.php b/config-templates/config.php index 418504084..e21bcd8b4 100644 --- a/config-templates/config.php +++ b/config-templates/config.php @@ -197,6 +197,61 @@ $config = array( ), + + /* + * Database + * + * This database configuration is optional. If you are not using + * core funcationlity or modules that require a database, you can + * skip this configuration. + */ + + /* + * Database connection string. + * Ensure that you have the required PDO database drive installed + * for your connection string. + */ + 'database.dsn' => 'mysql:host=localhost;dbname=saml', + + /* + * SQL database credentials + */ + 'database.username' => 'simplesamlphp', + 'database.password' => 'secret', + + /* + * (Optional) Table prefix + */ + 'database.prefix' => '', + + /* + * True or false if you would like a persisent datata connection + */ + 'database.persistent' => false, + + /* + * Database slave configuration is optional as well. If you are only + * running a single database server, leave this blank. If you have + * a master/slave conifguration, you can define as many slave servers + * as you want here. Slaves will be picked at random to be queried from. + * + * Configuration options in the salve array are exactly the same as the + * options for the master (shown above) with the exception of the table + * prefix. + */ + 'database.slaves' => array( + /* + array( + 'dsn' => 'mysql:host=myslave;dbname=saml', + 'username' => 'simplesamlphp', + 'password' => 'secret', + 'persistent' => false, + ), + */ + ), + + + /* * Enable * @@ -572,13 +627,13 @@ $config = array( * hours (86400 seconds). Optional. * * PDO metadata handler: - * This metadata handler looks up for the metadata of an entity stored in a database. + * This metadata handler looks up metadata of an entity stored in a database. + * + * Note: If you are using the PDO metadata handler, you must configure the database + * options in this configuration file. + * * The PDO metadata handler defines the following options: * - 'type': This is always 'pdo'. - * - 'dsn': The database connection string. Mandatory. - * - 'username': Database username - * - 'cachedir': Database password - * - 'usePersistentConnection': Enable/Disable persistent database connection. Default is false. * * * Examples: @@ -604,7 +659,7 @@ $config = array( * * This example defines an pdo source. * 'metadata.sources' => array( - * array('type' => 'pdo', server => 'mysql:host=localhost;dbname=saml', 'username' => 'simplesamlphp', 'password' => 'SuperSecretPassword') + * array('type' => 'pdo') * ), * * Default: diff --git a/lib/SimpleSAML/Database.php b/lib/SimpleSAML/Database.php new file mode 100644 index 000000000..16df90f5d --- /dev/null +++ b/lib/SimpleSAML/Database.php @@ -0,0 +1,202 @@ +<?php + +/** + * This file implements functions to read and write to a group of database + * servers. + * + * This database class supports a single database, or a master/slave + * configuration with as many defined slaves as a user would like. + * + * The goal of this class is to provide a single mechanism to connect to a database + * that can be reused by any component within SimpleSAMLphp including modules. + * When using this class, the global configuration should be passed here, but + * in the case of a module that has a good reason to use a different database, + * such as sqlauth, an alternative config file can be provided. + * + * @author Tyler Antonio, University of Alberta. <tantonio@ualberta.ca> + * @package simpleSAMLphp + */ + +class SimpleSAML_Database { + + /** + * This variable holds the instance of the session - Singleton approach. + */ + private static $instance = array(); + + /** + * PDO Object for the Master database server + */ + private $dbMaster; + + /** + * Array of PDO Objects for configured database + * slaves + */ + private $dbSlaves = array(); + + /** + * Prefix to apply to the tables + */ + private $tablePrefix; + + /** + * Retrieves the current database instance. Will create a new one if there isn't an existing connection. + * + * @param object $altConfig Optional: Instance of a SimpleSAML_Configruation class + * @return SimpleSAML_Database The shared database connection. + */ + public static function getInstance($altConfig = null) { + $config = ($altConfig)? $altConfig : SimpleSAML_Configuration::getInstance(); + $instanceId = spl_object_hash($config); + + /* Check if we already have initialized the session. */ + if (isset(self::$instance[$instanceId])) { + return self::$instance[$instanceId]; + } + + /* Create a new session. */ + self::$instance[$instanceId] = new SimpleSAML_Database($config); + return self::$instance[$instanceId]; + } + + /** + * Private constructor that restricts instantiation to getInstance(). + * + * @param object $config Instance of the SimpleSAML_Configruation class + */ + private function __construct($config) { + $driverOptions = array(); + if ($config->getBoolean('database.persistent', TRUE)) { + $driverOptions = array(PDO::ATTR_PERSISTENT => TRUE); + } + + // Connect to the master + $this->dbMaster = $this->connect($config->getValue('database.dsn'), $config->getValue('database.username'), $config->getValue('database.password'), $driverOptions); + + // Connect to any configured slaves + $slaves = $config->getValue('database.slaves'); + if (count($slaves >= 1)) { + foreach ($slaves as $slave) { + array_push($this->dbSlaves, $this->connect($slave['dsn'], $slave['username'], $slave['password'], $driverOptions)); + } + } + + $this->tablePrefix = $config->getString('database.prefix', ''); + } + + /** + * This function connects to a dabase. + * + * @param $dsn Database connection string + * @param $username SQL user + * @param $password SQL password + * @param $options PDO options + * + * @return PDO object + */ + private function connect($dsn, $username, $password, $options){ + try{ + $db = new PDO($dsn, $username, $password, $options); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + return $db; + } catch(PDOException $e){ + throw new Exception("Database error: ". $e->getMessage()); + } + } + + /** + * This function randomly selects a slave database server + * to query. In the event no slaves are configured, it + * will return the master. + * + * @return PDO object + */ + private function getSlave(){ + if (count($this->dbSlaves) > 0) { + $slaveId = rand(0,count($this->dbSlaves)-1); + return $this->dbSlaves[$slaveId]; + } else { + return $this->dbMaster; + } + } + + /** + * This function simply applies the table prefix to + * a suppled table name. + * + * @param $table Table to apply prefix,if configured + * @return string Table with configured prefix + */ + public function applyPrefix($table){ + return $this->tablePrefix . $table; + } + + /** + * This function queries the database + * + * @param $db PDO object to use + * @param $stmt Prepared SQL statement + * @param $params Parameters + * + * @return PDO statement object + */ + private function query($db, $stmt, $params){ + assert('is_object($db)'); + assert('is_string($stmt)'); + assert('is_array($params)'); + + try{ + $query = $db->prepare($stmt); + + foreach ($params as $param => $value) { + if(is_array($value)){ + $query->bindValue(":$param", $value[0], ($value[1])? $value[1] : PDO::PARAM_STR); + } + else{ + $query->bindValue(":$param", $value, PDO::PARAM_STR); + } + } + + $query->execute(); + + if ($query->execute() === FALSE) { + throw new Exception("Database error: " . var_export($this->pdo->errorInfo(), TRUE)); + } + + return $query; + } catch (PDOException $e){ + throw new Exception("Database error: ". $e->getMessage()); + } + } + + /** + * This executes queries directly on the master. + * + * @param $stmt Prepared SQL statement + * @param $params Parameters + * + * @return PDO statement object + */ + public function write($stmt, $params = array()){ + $db = $this->dbMaster; + + return $this->query($db, $stmt, $params); + } + + /** + * This executes queries on a database server + * that is determined by this::getSlave() + * + * @param $stmt Prepared SQL statement + * @param $params Parameters + * + * @return PDO statement object + */ + public function read($stmt, $params = array()){ + $db = $this->getSlave(); + + return $this->query($db, $stmt, $params); + } +} diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php index 7bde1847a..3a0030a69 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerPdo.php @@ -20,7 +20,7 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerPdo extends SimpleSAML_Metadata_ /** * The PDO object */ - private $pdo; + private $db; /** * Prefix to apply to the metadata table @@ -69,20 +69,9 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerPdo extends SimpleSAML_Metadata_ assert('is_array($config)'); $globalConfig = SimpleSAML_Configuration::getInstance(); + $this->db = SimpleSAML_Database::getInstance(); $cfgHelp = SimpleSAML_Configuration::loadFromArray($config, 'pdo metadata source'); - - // determine the table prefix if one was set - $this->tablePrefix = $cfgHelp->getString('tablePrefix', ''); - $this->dsn = $cfgHelp->getString('dsn'); - - $driverOptions = array(); - if ($cfgHelp->getBoolean('usePersistentConnection', TRUE)) { - $driverOptions = array(PDO::ATTR_PERSISTENT => TRUE); - } - - $this->pdo = new PDO($this->dsn, $cfgHelp->getValue('username', NULL), $cfgHelp->getValue('password', NULL), $driverOptions); - $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } @@ -102,7 +91,7 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerPdo extends SimpleSAML_Metadata_ return NULL; } - $stmt = $this->pdo->prepare("SELECT entity_id, entity_data FROM $tableName"); + $stmt = $this->db->read("SELECT entity_id, entity_data FROM $tableName"); if($stmt->execute()) { $metadata = array(); @@ -189,24 +178,24 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerPdo extends SimpleSAML_Metadata_ $tableName = $this->getTableName($set); - $metadata = $this->pdo->prepare("SELECT entity_id, entity_data FROM $tableName WHERE entity_id = :entity_id"); - $metadata->bindValue(":entity_id", $index, PDO::PARAM_STR); - $metadata->execute(); + $metadata = $this->db->read("SELECT entity_id, entity_data FROM $tableName WHERE entity_id = :entity_id", array( + 'entity_id' => $index, + )); + $retrivedEntityIDs = $metadata->fetch(); + $params = array( + 'entity_id' => $index, + 'entity_data' => json_encode($entityData), + ); + if($retrivedEntityIDs !== FALSE && count($retrivedEntityIDs) > 0){ - $stmt = $this->pdo->prepare("UPDATE $tableName SET entity_data = :entity_data WHERE entity_id = :entity_id"); + $stmt = $this->db->write("UPDATE $tableName SET entity_data = :entity_data WHERE entity_id = :entity_id", $params); } else{ - $stmt = $this->pdo->prepare("INSERT INTO $tableName (entity_id, entity_data) VALUES (:entity_id, :entity_data)"); + $stmt = $this->db->write("INSERT INTO $tableName (entity_id, entity_data) VALUES (:entity_id, :entity_data)", $params); } - $stmt->bindValue(":entity_id", $index, PDO::PARAM_STR); - $stmt->bindValue(":entity_data", json_encode($entityData), PDO::PARAM_STR); - - if ($stmt->execute() === FALSE) { - throw new Exception("PDO metadata handler: Database error: " . var_export($this->pdo->errorInfo(), TRUE)); - } return 1 === $stmt->rowCount(); } @@ -220,7 +209,7 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerPdo extends SimpleSAML_Metadata_ private function getTableName($table) { assert('is_string($table)'); - return str_replace("-", "_", $this->tablePrefix . $table); + return $this->db->applyPrefix(str_replace("-", "_", $this->tablePrefix . $table)); } /** @@ -229,11 +218,8 @@ class SimpleSAML_Metadata_MetaDataStorageHandlerPdo extends SimpleSAML_Metadata_ public function initDatabase() { foreach ($this->supportedSets as $set) { $tableName = $this->getTableName($set); - $result = $this->pdo->exec("CREATE TABLE IF NOT EXISTS $tableName (entity_id VARCHAR(255) PRIMARY KEY NOT NULL, entity_data TEXT NOT NULL)"); - if ($result === FALSE) { - throw new Exception("PDO metadata handler: Database error: " . var_export($this->pdo->errorInfo(), TRUE)); - } + $this->db->write("CREATE TABLE IF NOT EXISTS $tableName (entity_id VARCHAR(255) PRIMARY KEY NOT NULL, entity_data TEXT NOT NULL)"); } } -} +} \ No newline at end of file -- GitLab