diff --git a/framework/Auth/lib/Horde/Auth/Otp.php b/framework/Auth/lib/Horde/Auth/Otp.php
new file mode 100644
index 0000000..48c4340
--- /dev/null
+++ b/framework/Auth/lib/Horde/Auth/Otp.php
@@ -0,0 +1,272 @@
+<?php
+/**
+ * The Horde_Auth_Otp class provides a Hash-Chain implementation of the Horde
+ * authentication system.
+ *
+ * The table structure for the Auth system is in
+ * horde/scripts/sql/horde_users.sql.
+ *
+ * Copyright 1999-2012 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you did
+ * not receive this file, http://www.horde.org/licenses/lgpl21
+ *
+ * @author Alexandra Simon <alexandra.simon@hotmail.com>
+ * @author Carl Denis <c.denis@mrduck.fr>
+ * @category Horde
+ * @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
+ * @package Auth
+ */
+class Horde_Auth_Otp extends Horde_Auth_Base
+{
+ /**
+ * An array of capabilities, so that the driver can report which
+ * operations it supports and which it doesn't.
+ *
+ * @var array
+ */
+ protected $_capabilities = array(
+ 'authenticate' => true,
+ );
+
+ /**
+ * Handle for the current database connection.
+ *
+ * @var Horde_Db_Adapter
+ */
+ protected $_db;
+
+ /**
+ * Constructor
+ *
+ * @param array $params Parameters:
+ * 'db' - (Horde_Db_Adapter) [REQUIRED] Database object.
+ * <pre>
+ * 'encryption' - (string) The encryption to use to store the password in
+ * the table (e.g. crypt, md5-hex, md5-base64, smd5,
+ * sha, ssha, aprmd5).
+ * DEFAULT: 'md5-hex'
+ * 'hard_expiration_field' - (string) The name of the field containing a
+ * date after which the account is no longer
+ * valid and the user will not be able to log in
+ * at all.
+ * DEFAULT: none
+ * 'password_field' - (string) The name of the password field in the auth
+ * table.
+ * DEFAULT: 'hash_chain_pwd'
+ * 'index_field' - (string) The name of the field containing the count of
+ * left one time passwords in the current hash chain.
+ * DEFAULT: 'hash_chain_index'
+ * 'show_encryption' - (boolean) Whether or not to prepend the encryption
+ * in the password field.
+ * DEFAULT: false
+ * 'soft_expiration_field' - (string) The name of the field containing a
+ * date after which the system will request the
+ * user change his or her password.
+ * DEFAULT: none
+ * 'table' - (string) The name of the SQL table to use in 'database'.
+ * DEFAULT: 'horde_users'
+ * 'username_field' - (string) The name of the username field in the auth
+ * table.
+ * DEFAULT: 'user_uid'
+ * </pre>
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct(array $params = array())
+ {
+ if (!isset($params['db'])) {
+ throw new InvalidArgumentException('Missing db parameter.');
+ }
+
+ if (isset($params['encryption']) && $params['encryption'] == 'plain')
+ {
+ throw new InvalidArgumentException('Plain encryption cannot be used for hash-chain one time password.');
+ }
+
+ $this->_db = $params['db'];
+ unset($params['db']);
+
+ $params = array_merge(array(
+ 'encryption' => 'md5-hex',
+ 'password_field' => 'hash_chain_pwd',
+ 'index_field' => 'hash_chain_index',
+ 'show_encryption' => false,
+ 'table' => 'horde_users',
+ 'username_field' => 'user_uid',
+ 'soft_expiration_field' => null,
+ 'soft_expiration_window' => null,
+ 'hard_expiration_field' => null,
+ 'hard_expiration_window' => null
+ ), $params);
+
+ parent::__construct($params);
+
+ /* If enabled, uses limits defined by the SQL Authentication driver,
+ * as it uses the same table in the database
+ */
+ if ((empty($params['soft_expiration_field'])) &&
+ ($params['soft_expiration_window'] > 0)) {
+ throw new InvalidArgumentException('You cannot set [soft_expiration_window] without [soft_expiration_field].');
+ }
+
+ if (($params['hard_expiration_field'] == '') &&
+ ($params['hard_expiration_window'] > 0)) {
+ throw new InvalidArgumentException('You cannot set [hard_expiration_window] without [hard_expiration_field].');
+ }
+
+ }
+
+ /**
+ * Find out if a set of login credentials are valid.
+ *
+ * @param string $userId The userId to check.
+ * @param array $credentials The credentials to use.
+ *
+ * @throws Horde_Auth_Exception
+ */
+ protected function _authenticate($userId, $credentials)
+ {
+ /* Build the SQL query. */
+ $query = sprintf('SELECT * FROM %s WHERE %s = ?',
+ $this->_params['table'],
+ $this->_params['username_field']);
+ $values = array($userId);
+
+ try {
+ $row = $this->_db->selectOne($query, $values);
+ } catch (Horde_Db_Exception $e) {
+ throw new Horde_Auth_Exception('', Horde_Auth::REASON_FAILED);
+ }
+
+ if ((!$row) ||
+ (!$this->_comparePasswords($row[$this->_params['password_field']], $credentials['password'])) ||
+ (!$row[$this->_params['index_field']] > 0) ) {
+ throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN);
+ }
+
+ $this->setNextPassword($userId, $credentials['password'], $row[$this->_params['index_field']]-1 );
+
+ $now = time();
+ if (!empty($this->_params['hard_expiration_field']) &&
+ !empty($row[$this->_params['hard_expiration_field']]) &&
+ ($now > $row[$this->_params['hard_expiration_field']])) {
+ throw new Horde_Auth_Exception('', Horde_Auth::REASON_EXPIRED);
+ }
+
+ if (!empty($this->_params['soft_expiration_field']) &&
+ !empty($row[$this->_params['soft_expiration_field']]) &&
+ ($now > $row[$this->_params['soft_expiration_field']])) {
+ $this->setCredential('change', true);
+ $this->setCredential('expire', $date);
+ }
+ }
+
+
+ /**
+ * Set the next password in the hashchain.
+ * Reduce the counter of left passwords.
+ *
+ * @param string $userID The user ID.
+ * @param array $credentials The new password and next index value.
+ *
+ * @throws Horde_Auth_Exception
+ */
+ public function setNextPassword($userID, $password, $index)
+ {
+ $query = sprintf('UPDATE %s SET ', $this->_params['table']);
+ $values = array();
+
+ /* Build the SQL query. */
+ $query .= $this->_params['password_field'] . ' = ?';
+ $values[] = $password;
+
+ $query .= ', ' . $this->_params['index_field'] . ' = ?';
+ $values[]= $index;
+
+ $query .= sprintf(' WHERE %s = ?', $this->_params['username_field']);
+ $values[] = $userID;
+ try {
+ $this->_db->update($query, $values);
+ } catch (Horde_Db_Exception $e) {
+ throw new Horde_Auth_Exception($e);
+ }
+ }
+
+ /**
+ * Checks if a userId exists in the system.
+ *
+ * @return boolean Whether or not the userId already exists.
+ */
+ public function exists($userId)
+ {
+ /* Build the SQL query. */
+ $query = sprintf('SELECT 1 FROM %s WHERE %s = ?',
+ $this->_params['table'],
+ $this->_params['username_field']);
+ $values = array($userId);
+
+ try {
+ return (bool)$this->_db->selectValue($query, $values);
+ } catch (Horde_Db_Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Compare an encrypted password to a plaintext string to see if
+ * they match.
+ *
+ * @param string $encrypted The crypted password to compare against.
+ * @param string $plaintext The plaintext password to verify.
+ *
+ * @return boolean True if matched, false otherwise.
+ */
+ protected function _comparePasswords($encrypted, $plaintext)
+ {
+ return $encrypted == $this->_reduce_password(Horde_Auth::getCryptedPassword($plaintext,
+ $encrypted,
+ $this->_params['encryption'],
+ $this->_params['show_encryption']));
+ }
+
+ /**
+ * Calculate a timestamp and return it along with the field name
+ *
+ * @param string $type The timestamp parameter.
+ *
+ * @return integer 'timestamp' intended field value or null
+ */
+ private function _calc_expiration($type)
+ {
+ if (empty($this->_params[$type . '_expiration_window'])) {
+ return null;
+ } else {
+ $now = new Horde_Date(time());
+ return $now->add(array('mday' => $this->_params[$type.'_expiration_window']))->timestamp();
+ }
+ }
+
+ /**
+ * Transform a password hash in a Form, that can easily be entered
+ * by either reducing the length or using another encoding alphabet
+ *
+ * @param string $hash The password to make userfriendly
+ * @param array $outformat The format to set the password to
+ * DEFAULT: none
+ *
+ * @return string The modified hash to fit a specific format
+ */
+ private function _reduce_password($hash)
+ {
+ switch ($this->_params['pwdformat']) {
+ // other cases need to be added to reduce length
+ // e.g. HEX to [a-zA-Z0-9] or just truncate
+ case 'truncate':
+ $len = isset($this->_params['pwdformat_length']) ? $this->_params['pwdformat_length'] : 8;
+ return substr($hash,0,$len);
+ default:
+ return $hash;
+ }
+ }
+}
diff --git a/framework/Core/lib/Horde/Core/Factory/Auth.php b/framework/Core/lib/Horde/Core/Factory/Auth.php
index 1d412fc..bef7100 100644
--- a/framework/Core/lib/Horde/Core/Factory/Auth.php
+++ b/framework/Core/lib/Horde/Core/Factory/Auth.php
@@ -183,6 +183,18 @@ class Horde_Core_Factory_Auth extends Horde_Core_Factory_Base
->create('horde', is_null($orig_params) ? 'auth' : $orig_params);
}
break;
+ case 'otp':
+ if (!empty($params['driverconfig']) &&
+ $params['driverconfig'] == 'horde') {
+ $params['db'] = $this->_injector
+ ->getInstance('Horde_Db_Adapter');
+ } else {
+ $params['db'] = $this->_injector
+ ->getInstance('Horde_Core_Factory_Db')
+ ->create('horde', is_null($orig_params) ? 'auth' : $orig_params);
+ }
+ break;
+
}
$params['default_user'] = $GLOBALS['registry']->getAuth();