Import Horde_Token from CVS HEAD
authorMichael M Slusarz <slusarz@curecanti.org>
Sun, 22 Feb 2009 09:17:40 +0000 (02:17 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Sun, 22 Feb 2009 09:17:40 +0000 (02:17 -0700)
framework/Token/lib/Horde/Token.php [new file with mode: 0644]
framework/Token/lib/Horde/Token/File.php [new file with mode: 0644]
framework/Token/lib/Horde/Token/Sql.php [new file with mode: 0644]
framework/Token/package.xml [new file with mode: 0644]

diff --git a/framework/Token/lib/Horde/Token.php b/framework/Token/lib/Horde/Token.php
new file mode 100644 (file)
index 0000000..0ab3ebb
--- /dev/null
@@ -0,0 +1,185 @@
+<?php
+/**
+ * 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.
+ *
+ * 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 <max@horde.org>
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @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:
+     * <code>$var = Horde_Token::singleton();</code>
+     *
+     * @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 (file)
index 0000000..4e61d76
--- /dev/null
@@ -0,0 +1,172 @@
+<?php
+/**
+ * Token tracking implementation for local files.
+ *
+ * Optional parameters:<pre>
+ *   '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).</pre>
+ *
+ * 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 <max@horde.org>
+ * @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 (file)
index 0000000..7759d0d
--- /dev/null
@@ -0,0 +1,246 @@
+<?php
+/**
+ * Token tracking implementation for PHP's PEAR database abstraction layer.
+ *
+ * Required parameters:<pre>
+ *   'phptype'      The database type (ie. 'pgsql', 'mysql', etc.).</pre>
+ *
+ * Required by some database implementations:<pre>
+ *   '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.</pre>
+ *
+ * Optional parameters:<pre>
+ *   '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).</pre>
+ *
+ * Optional values when using separate reading and writing servers, for example
+ * in replication settings:<pre>
+ *   '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.</pre>
+ *
+ * The table structure for the tokens is as follows:
+ *
+ * <pre>
+ * 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)
+ * );
+ * </pre>
+ *
+ * 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 <max@horde.org>
+ * @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 (file)
index 0000000..30943df
--- /dev/null
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Token</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Token API</summary>
+ <description>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.
+ </description>
+ <lead>
+  <name>Chuck Hagenbuch</name>
+  <user>chuck</user>
+  <email>chuck@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <date>2009-02-21</date>
+ <version>
+  <release>0.1.0</release>
+  <api>0.1.0</api>
+ </version>
+ <stability>
+  <release>beta</release>
+  <api>beta</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Initial Horde 4 package.
+ </notes>
+ <contents>
+  <dir name="/">
+   <dir name="/lib">
+    <dir name="/lib/Horde">
+     <dir name="/lib/Horde/Token">
+      <file name="File.php" role="php" />
+      <file name="Sql.php" role="php" />
+     </dir> <!-- /lib/Horde/Token -->
+     <file name="Token.php" role="php" />
+    </dir> <!-- /lib/Horde -->
+   </dir> <!-- /lib -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.2.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.5.0</min>
+   </pearinstaller>
+   <package>
+    <name>Util</name>
+    <channel>pear.horde.org</channel>
+   </package>
+  </required>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install name="lib/Horde/Token/File.php" as="Horde/Token/File.php" />
+   <install name="lib/Horde/Token/Sql.php" as="Horde/Token/Sql.php" />
+   <install name="lib/Horde/Token.php" as="Horde/Token.php" />
+  </filelist>
+ </phprelease>
+ <changelog>
+  <release>
+   <version>
+    <release>0.0.4</release>
+    <api>0.0.4</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2006-05-08</date>
+   <time>23:48:27</time>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>Converted to package.xml 2.0 for pear.horde.org
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.0.3</release>
+    <api>0.0.3</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2005-01-01</date>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>* Add support for separate read and write DB servers for the sql driver.
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.0.2</release>
+    <api>0.0.2</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2004-11-23</date>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>Remove assumption of IPv4 IP address format
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.0.1</release>
+    <api>0.0.1</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <date>2003-07-03</date>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>Initial release as a PEAR package
+   </notes>
+  </release>
+ </changelog>
+</package>