From d1c22b2bdbe60773c6945daca2cf78c0098e4028 Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Sun, 22 Feb 2009 02:17:40 -0700 Subject: [PATCH] Import Horde_Token from CVS HEAD --- framework/Token/lib/Horde/Token.php | 185 +++++++++++++++++++++++ framework/Token/lib/Horde/Token/File.php | 172 +++++++++++++++++++++ framework/Token/lib/Horde/Token/Sql.php | 246 +++++++++++++++++++++++++++++++ framework/Token/package.xml | 124 ++++++++++++++++ 4 files changed, 727 insertions(+) create mode 100644 framework/Token/lib/Horde/Token.php create mode 100644 framework/Token/lib/Horde/Token/File.php create mode 100644 framework/Token/lib/Horde/Token/Sql.php create mode 100644 framework/Token/package.xml diff --git a/framework/Token/lib/Horde/Token.php b/framework/Token/lib/Horde/Token.php new file mode 100644 index 000000000..0ab3ebb35 --- /dev/null +++ b/framework/Token/lib/Horde/Token.php @@ -0,0 +1,185 @@ + + * @author Chuck Hagenbuch + * @package Horde_Token + */ +class Horde_Token +{ + /** + * Singleton instances. + * + * @var array + */ + static protected $_instances = array(); + + /** + * Hash of parameters necessary to use the chosen backend. + * + * @var array + */ + protected $_params = array(); + + /** + * Attempts to return a concrete Horde_Token instance based on $driver. + * + * @param mixed $driver The type of concrete Horde_Token subclass to + * return. If $driver is an array, then we will look + * in $driver[0]/lib/Token/ for the subclass + * implementation named $driver[1].php. + * @param array $params A hash containing any additional configuration or + * connection parameters a subclass might need. + * + * @return Horde_Token The newly created concrete Horde_Token instance, or + * false an error. + */ + static public function getInstance($driver, $params = array()) + { + if (is_array($driver)) { + list($app, $driver) = $driver; + } + + $driver = basename($driver); + $class = ($driver == 'none') + ? 'Horde_Token'; + : 'Horde_Token_' . ucfirst($driver); + + if (!class_exists($class)) { + /* If driver class doesn't exist or the driver is not + * available just default to the parent class, and it is + * not necessary to warn about degraded service. */ + $class = 'Horde_Token'; + } + + return new $class($params); + } + + /** + * Attempts to return a reference to a concrete Horde_Token instance based + * on $driver. + * + * It will only create a new instance if no Horde_Token instance with the + * same parameters currently exists. + * + * This should be used if multiple types of token generators (and, thus, + * multiple Horde_Token instances) are required. + * + * This method must be invoked as: + * $var = Horde_Token::singleton(); + * + * @param mixed $driver The type of concrete Horde_Token subclass to + * return. If $driver is an array, then we will look + * in $driver[0]/lib/Token/ for the subclass + * implementation named $driver[1].php. + * @param array $params A hash containing any additional configuration or + * connection parameters a subclass might need. + * + * @return Horde_Token The concrete Horde_Token reference, or false on + * error. + */ + static public function singleton($driver, $params = array()) + { + ksort($params); + $sig = hash('md5', serialize(array($driver, $params))); + + if (!isset(self::$_instances[$sig])) { + self::$_instances[$sig] = Horde_Token::getInstance($driver, $params); + } + + return self::$_instances[$sig]; + } + + /** + * Constructor. + */ + protected function __construct($params) + { + $this->_params = $params; + } + + /** + * TODO + */ + public function encodeRemoteAddress() + { + return isset($_SERVER['REMOTE_ADDR']) + ? base64_encode($_SERVER['REMOTE_ADDR']) + : ''; + } + + /** + * Generates a connection id and returns it. + * + * @param string $seed A unique ID to be included in the token. + * + * @return string The generated id string. + */ + public function generateId($seed = '') + { + require_once 'Horde/Util.php'; + return Util::uriB64Encode(pack('H*', sha1(uniqid(mt_rand(), true) . $seed . (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '')))); + } + + /** + * Checks if the given token has been previously used. First + * purges all expired tokens. Then retrieves current tokens for + * the given ip address. If the specified token was not found, + * adds it. + * + * @param string $token The value of the token to check. + * + * @return boolean True if the token has not been used, false otherwise. + */ + public function verify($token) + { + $this->purge(); + $exists = $this->exists($token); + if (is_a($exists, 'PEAR_Error')) { + return $exists; + } elseif ($exists) { + return false; + } else { + return $this->add($token); + } + } + + /** + * This is an abstract method that should be overridden by a + * subclass implementation. The base implementation allows all + * token values. + */ + public function exists() + { + return false; + } + + /** + * This is an abstract method that should be overridden by a + * subclass implementation. The base implementation allows all + * token values. + */ + public function add() + { + return true; + } + + /** + * This is an abstract method that should be overridden by a + * subclass implementation. The base implementation allows all + * token values. + */ + public function purge() + { + return true; + } + +} diff --git a/framework/Token/lib/Horde/Token/File.php b/framework/Token/lib/Horde/Token/File.php new file mode 100644 index 000000000..4e61d76d3 --- /dev/null +++ b/framework/Token/lib/Horde/Token/File.php @@ -0,0 +1,172 @@ + + * 'token_dir' The directory where to keep token files. + * 'timeout' The period (in seconds) after which an id is purged. + * Defaults to 86400 (i.e. 24 hours). + * + * Copyright 1999-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Max Kalika + * @package Horde_Token + */ +class Horde_Token_file extends Horde_Token +{ + /** + * Handle for the open file descriptor. + * + * @var resource + */ + protected $_fd = false; + + /** + * Boolean indicating whether or not we have an open file descriptor. + * + * @var boolean + */ + protected $_connected = false; + + /** + * Create a new file based token-tracking container. + * + * @param array $params A hash containing storage parameters. + */ + protected function __construct($params = array()) + { + parent::__construct($params); + + /* Choose the directory to save the stub files. */ + if (!isset($this->_params['token_dir'])) { + $this->_params['token_dir'] = Util::getTempDir(); + } + + /* Set timeout to 24 hours if not specified. */ + if (!isset($this->_params['timeout'])) { + $this->_params['timeout'] = 86400; + } + } + + /** + * Destructor. + */ + protected function __destruct() + { + $this->_disconnect(); + } + + /** + * Deletes all expired connection id's from the SQL server. + * + * @return boolean True on success, a PEAR_Error object on failure. + */ + public function purge() + { + // Make sure we have no open file descriptors before unlinking + // files. + if (!$this->_disconnect()) { + return PEAR::raiseError('Unable to close file descriptors'); + } + + /* Build stub file list. */ + if (!($dir = opendir($this->_params['token_dir']))) { + return PEAR::raiseError('Unable to open token directory'); + } + + /* Find expired stub files */ + while (($dirEntry = readdir($dir)) != '') { + if (preg_match('|^conn_\w{8}$|', $dirEntry) && (time() - filemtime($this->_params['token_dir'] . '/' . $dirEntry) >= $this->_params['timeout'])) { + if (!@unlink($this->_params['token_dir'] . '/' . $dirEntry)) { + return PEAR::raiseError('Unable to purge token file.'); + } + } + } + + closedir($dir); + return true; + } + + /** + * TODO + */ + public function exists($tokenID) + { + if (is_a(($result = $this->_connect($tokenID)), 'PEAR_Error')) { + return $result; + } + + /* Find already used IDs. */ + $fileContents = file($this->_params['token_dir'] . '/conn_' . $this->encodeRemoteAddress()); + if ($fileContents) { + $iMax = count($fileContents); + for ($i = 0; $i < $iMax; $i++) { + if (chop($fileContents[$i]) == $tokenID) { + return true; + } + } + } + + return false; + } + + /** + * TODO + */ + public function add($tokenID) + { + if (is_a(($result = $this->_connect($tokenID)), 'PEAR_Error')) { + return $result; + } + + /* Write the entry. */ + fwrite($this->_fd, "$tokenID\n"); + + /* Return an error if the update fails, too. */ + if (!$this->_disconnect()) { + return PEAR::raiseError('Failed to close token file cleanly.'); + } + + return true; + } + + /** + * Opens a file descriptor to a new or existing file. + * + * @return boolean True on success, a PEAR_Error object on failure. + */ + protected function _connect($tokenID) + { + if (!$this->_connected) { + + // Open a file descriptor to the token stub file. + $this->_fd = @fopen($this->_params['token_dir'] . '/conn_' . $this->encodeRemoteAddress(), 'a'); + if (!$this->_fd) { + return PEAR::raiseError('Failed to open token file.'); + } + + $this->_connected = true; + } + + return true; + } + + /** + * Closes the file descriptor. + * + * @return boolean True on success, false on failure. + */ + protected function _disconnect() + { + if ($this->_connected) { + $this->_connected = false; + return fclose($this->_fd); + } + + return true; + } + +} diff --git a/framework/Token/lib/Horde/Token/Sql.php b/framework/Token/lib/Horde/Token/Sql.php new file mode 100644 index 000000000..7759d0d49 --- /dev/null +++ b/framework/Token/lib/Horde/Token/Sql.php @@ -0,0 +1,246 @@ + + * 'phptype' The database type (ie. 'pgsql', 'mysql', etc.). + * + * Required by some database implementations:
+ *   'database'     The name of the database.
+ *   'hostspec'     The hostname of the database server.
+ *   'username'     The username with which to connect to the database.
+ *   'password'     The password associated with 'username'.
+ *   'options'      Additional options to pass to the database.
+ *   'tty'          The TTY on which to connect to the database.
+ *   'port'         The port on which to connect to the database.
+ * + * Optional parameters:
+ *   'table'        The name of the tokens table in 'database'.
+ *                  Defaults to 'horde_tokens'.
+ *   'timeout'      The period (in seconds) after which an id is purged.
+ *                  Defaults to 86400 (i.e. 24 hours).
+ * + * Optional values when using separate reading and writing servers, for example + * in replication settings:
+ *   'splitread'   Boolean, whether to implement the separation or not.
+ *   'read'        Array containing the parameters which are different for
+ *                 the read database connection, currently supported
+ *                 only 'hostspec' and 'port' parameters.
+ * + * The table structure for the tokens is as follows: + * + *
+ * CREATE TABLE horde_tokens (
+ *     token_address    VARCHAR(100) NOT NULL,
+ *     token_id         VARCHAR(32) NOT NULL,
+ *     token_timestamp  BIGINT NOT NULL,
+ *
+ *     PRIMARY KEY (token_address, token_id)
+ * );
+ * 
+ * + * Copyright 1999-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Max Kalika + * @package Horde_Token + */ +class Horde_Token_sql extends Horde_Token +{ + /** + * Handle for the current database connection. + * + * @var DB + */ + protected $_db = ''; + + /** + * Handle for the current database connection, used for writing. Defaults + * to the same handle as $_db if a separate write database is not required. + * + * @var DB + */ + protected $_write_db; + + /** + * Boolean indicating whether or not we're connected to the SQL + * server. + * + * @var boolean + */ + protected $_connected = false; + + /** + * Constructs a new SQL connection object. + * + * @param array $params A hash containing connection parameters. + */ + protected function __construct($params = array()) + { + parent::__construct($params); + + /* Set timeout to 24 hours if not specified. */ + if (!isset($this->_params['timeout'])) { + $this->_params['timeout'] = 86400; + } + } + + /** + * Deletes all expired connection id's from the SQL server. + * + * @return boolean True on success, a PEAR_Error object on failure. + */ + public function purge() + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + /* Build SQL query. */ + $query = 'DELETE FROM ' . $this->_params['table'] + . ' WHERE token_timestamp < ?'; + + $values = array(time() - $this->_params['timeout']); + + /* Return an error if the update fails. */ + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + + return true; + } + + /** + * TODO + */ + public function exists($tokenID) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return false; + } + + /* Build SQL query. */ + $query = 'SELECT token_id FROM ' . $this->_params['table'] + . ' WHERE token_address = ? AND token_id = ?'; + + $values = array($this->encodeRemoteAddress(), $tokenID); + + $result = $this->_db->getOne($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return false; + } else { + return !empty($result); + } + } + + /** + * TODO + */ + public function add($tokenID) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + /* Build SQL query. */ + $query = 'INSERT INTO ' . $this->_params['table'] + . ' (token_address, token_id, token_timestamp)' + . ' VALUES (?, ?, ?)'; + + $values = array($this->encodeRemoteAddress(), $tokenID, time()); + + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + + return true; + } + + /** + * Opens a connection to the SQL server. + * + * @return boolean True on success, a PEAR_Error object on failure. + */ + protected function _connect() + { + if ($this->_connected) { + return true; + } + + $result = Util::assertDriverConfig($this->_params, array('phptype'), + 'token SQL', array('driver' => 'token')); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + if (!isset($this->_params['database'])) { + $this->_params['database'] = ''; + } + if (!isset($this->_params['username'])) { + $this->_params['username'] = ''; + } + if (!isset($this->_params['password'])) { + $this->_params['password'] = ''; + } + if (!isset($this->_params['hostspec'])) { + $this->_params['hostspec'] = ''; + } + if (!isset($this->_params['table'])) { + $this->_params['table'] = 'horde_tokens'; + } + + /* Connect to the SQL server using the supplied parameters. */ + require_once 'DB.php'; + $this->_write_db = &DB::connect($this->_params, + array('persistent' => !empty($this->_params['persistent']), + 'ssl' => !empty($this->_params['ssl']))); + if (is_a($this->_write_db, 'PEAR_Error')) { + return $this->_write_db; + } + + // Set DB portability options. + switch ($this->_write_db->phptype) { + case 'mssql': + $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); + break; + default: + $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); + } + + /* Check if we need to set up the read DB connection + * seperately. */ + if (!empty($this->_params['splitread'])) { + $params = array_merge($this->_params, $this->_params['read']); + $this->_db = &DB::connect($params, + array('persistent' => !empty($params['persistent']), + 'ssl' => !empty($this->_params['ssl']))); + if (is_a($this->_db, 'PEAR_Error')) { + return $this->_db; + } + + // Set DB portability options. + switch ($this->_db->phptype) { + case 'mssql': + $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); + break; + default: + $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); + } + + } else { + /* Default to the same DB handle for read. */ + $this->_db = $this->_write_db; + } + + $this->_connected = true; + return true; + } + +} diff --git a/framework/Token/package.xml b/framework/Token/package.xml new file mode 100644 index 000000000..30943df1b --- /dev/null +++ b/framework/Token/package.xml @@ -0,0 +1,124 @@ + + + Token + pear.horde.org + Horde Token API + The Horde_Token:: class provides a common abstracted interface + into the various token generation mediums. It also includes all of the + functions for retrieving, storing, and checking tokens. + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + 2009-02-21 + + 0.1.0 + 0.1.0 + + + beta + beta + + LGPL + * Initial Horde 4 package. + + + + + + + + + + + + + + + + + + 5.2.0 + + + 1.5.0 + + + Util + pear.horde.org + + + + + + + + + + + + + + 0.0.4 + 0.0.4 + + + beta + beta + + 2006-05-08 + + LGPL + Converted to package.xml 2.0 for pear.horde.org + + + + + 0.0.3 + 0.0.3 + + + beta + beta + + 2005-01-01 + LGPL + * Add support for separate read and write DB servers for the sql driver. + + + + + 0.0.2 + 0.0.2 + + + beta + beta + + 2004-11-23 + LGPL + Remove assumption of IPv4 IP address format + + + + + 0.0.1 + 0.0.1 + + + alpha + alpha + + 2003-07-03 + LGPL + Initial release as a PEAR package + + + + -- 2.11.0