Modified:
trunk/Lux/Auth/Adapter/Psql.php
Log:
Lux_Auth_Adapter_Psql: Major refactoring
* [NEW] You can now specify if you want users to be remembered once vs. forever.
* [CHG] Only *one* timeout value used for both server-side timestamp
and the cookie itself.
* [CHG] Using multiWhere() to find the user info instead of afterwards
comparing all the values with PHP. Thanks, Rodrigo.
* [NEW] Added a few helper methods.
Modified: trunk/Lux/Auth/Adapter/Psql.php
==============================================================================
--- trunk/Lux/Auth/Adapter/Psql.php (original)
+++ trunk/Lux/Auth/Adapter/Psql.php Mon Mar 3 13:01:19 2008
@@ -4,7 +4,9 @@
* Persistent authentication using cookies and a SQL database table.
*
* Follows Chris Shiflett's recommendations in "Essential PHP
Security" -
- * chapter 7.4. Persistent Logins.
+ * chapter 7.4. Persistent Logins. You can decide if you want a user
+ * to be remembered **once**, or forever as long as they re-authenticate
+ * within a certain time-window.
*
* @category Lux
*
@@ -38,20 +40,23 @@
* : (string) Persist key in the credential array source, default 'persist'.
*
* `timeout`
- * : (int) Token lifetime in seconds. After this time, it is reset and
- * saved again in the database. Default is 86400 (one day).
+ * : (int) Time in seconds in which time user's cookie is valid
+ * for authentication. This value is used for the server-side
+ * timestamp as well as for the cookie itself. Timestamp will be
+ * created by adding this value to time(). Default is 86400 (one day).
*
* `hash_algo`
* : (string) Name of the hashing algorithm (i.e. 'md5', 'sha256',
* 'haval160,4' etc.). Default is 'md5'.
*
+ * `cookie_renew`
+ * : (bool) Whether a new auth cookie should be sent after a successful
+ * cookie authentication. Setting this to `false`
means "remember me once".
+ * Otherwise, always "remember me again". Default is true.
+ *
* `cookie_name`
* : (string) Name of the cookie.
*
- * `cookie_expire`
- * : (int) Expiration time for the cookie, in seconds. Default is 2592000
- * (one week).
- *
* `cookie_path`
* : (string) Path option for setcookie().
*
@@ -73,10 +78,10 @@
'token_col' => 'token',
'timeout_col' => 'timeout',
'source_persist' => 'persist',
- 'timeout' => 604800,
+ 'timeout' => 86400,
'hash_algo' => 'md5',
+ 'cookie_renew' => true,
'cookie_name' => 'auth',
- 'cookie_expire' => 2592000,
'cookie_path' => '/',
'cookie_domain' => '',
'cookie_secure' => false,
@@ -124,6 +129,36 @@
/**
*
+ * Verifies an username handle and password.
+ *
+ * @return mixed An array of verified user information, or boolean false
+ * if verification failed.
+ *
+ */
+ protected function _processLogin()
+ {
+ $info = parent::_processLogin();
+
+ // was this a successful login?
+ if ($info) {
+ // check to see if user wants persistent auth
+ $method = strtolower($this->_config['source']);
+ $persist = (bool) $this->_request->$method(
+ $this->_config['source_persist'],
+ false
+ );
+
+ if ($persist) {
+ $handle = $info[$this->_config['handle_col']];
+ $this->_renewCookie($handle);
+ }
+ }
+
+ return $info;
+ }
+
+ /**
+ *
* Processes a cookie login attempt and sets user credentials.
*
* @return bool True if the login was successful, false if not.
@@ -135,17 +170,21 @@
$cookie =
$this->_request->cookie($this->_config['cookie_name'], false);
if ($cookie) {
-
- // cookie
+ // parse cookie
list($identifier, $token) = explode(':', $cookie);
+ // sanity check
+ if (empty($identifier) || empty($token)) {
+ return false;
+ }
+
// get the dependency object of class Solar_Sql
- $obj = Solar::dependency('Solar_Sql', $this->_config['sql']);
+ $sql = Solar::dependency('Solar_Sql', $this->_config['sql']);
// get a selection tool using the dependency object
$select = Solar::factory(
'Solar_Sql_Select',
- array('sql' => $obj)
+ array('sql' => $sql)
);
// list of optional columns as (property => field)
@@ -171,125 +210,100 @@
}
}
+ $identifier_col = $this->_config['identifier_col'];
+ $token_col = $this->_config['token_col'];
+ $timeout_col = $this->_config['timeout_col'];
+
// build select
$select->from($this->_config['table'])
->cols($cols)
- ->where("{$this->_config['identifier_col']} = ?", $identifier);
+ ->multiWhere(array(
+ "$identifier_col = ?" => $identifier,
+ "$token_col = ?" => $token,
+ "$timeout_col > ?" => time(),
+ ));
// fetch one row
$data = $select->fetch('one');
if ($data) {
- $identifier_real = hash(
- $this->_config['hash_algo'],
- $this->_config['salt']
- . hash($this->_config['hash_algo'], $data[$this->_config['handle_col']])
- );
-
- $token_check = $data[$this->_config['token_col']]
== $token;
- $identifier_check =
$data[$this->_config['identifier_col']] == $identifier_real;
-
- // if any of these fail we'll just return false
- if (! $token_check || ! $identifier_check) {
- return false;
- }
-
- // Renew cookie if necessary.
- $timeout_check = $data[$this->_config['timeout_col']]
< date('Y-m-d H:i:s');
+ $handle = $data[$this->_config['handle_col']];
- if (! $timeout_check) {
- $handle = $data[$this->_config['handle_col']];
- $this->_setToken($handle);
+ // successful re-authentication with a cookie!!!
+ // send a new cookie?
+ if ($this->_config['cookie_renew']) {
+ // yes, new cookie
+ $this->_renewCookie($handle);
+ } else {
+ // this was a one-time auth cookie.
+ // we must set the table to reflect "do not remember".
+ // this means two things:
+ // 1. reset timeout in db
+ // 2. delete the cookie
+
+ // 1.
+ $sql->update(
+ $this->_config['table'],
+ array($this->_config['timeout_col'] => time()),
+ array("{$this->_config['handle_col']} = ?" => $handle)
+ );
+
+ // 2.
+ $this->_setCookie('DELETED', time());
}
// successful login, treat result as user info
$this->reset('VALID', $data);
return true;
}
-
- // invalid identifier!
}
+ // fail in every other case
return false;
}
/**
*
- * Verifies an username handle and password.
- *
- * @return mixed An array of verified user information, or boolean false
- * if verification failed.
- *
- */
- protected function _processLogin()
- {
- $info = parent::_processLogin();
-
- // was this a successful login?
- if ($info) {
-
- // check to see if user wants persistent auth
- $method = strtolower($this->_config['source']);
- $persist = (bool) $this->_request->$method(
- $this->_config['source_persist'],
- false
- );
-
- if ($persist) {
- $handle = $info[$this->_config['handle_col']];
- $this->_setToken($handle);
- }
- }
-
- return $info;
- }
-
- /**
- *
* Creates and saves a new token in the database and sets/renews a cookie.
*
- * @param string $handle User handle.
+ * @param string $handle User handle
*
*/
- protected function _setToken($handle)
+ protected function _renewCookie($handle)
{
// generate identifier
- $identifier = hash(
- $this->_config['hash_algo'],
- $this->_config['salt']
- . hash($this->_config['hash_algo'], $handle)
- );
-
- // generate secret token
- $token = md5(uniqid(rand(), true));
-
+ $identifier = $this->_createIdentifier($handle);
+
+ // generate a secret token
+ $token = hash($this->_config['hash_algo'], uniqid(rand(), true));
+
// set timeout timestamp for the token (in seconds)
$timeout = time() + $this->_config['timeout'];
-
+
// update table info
// get the dependency object of class Solar_Sql
$sql = Solar::dependency('Solar_Sql', $this->_config['sql']);
-
+
+ // data to be updated
$data = array(
$this->_config['identifier_col'] => $identifier,
$this->_config['token_col'] => $token,
$this->_config['timeout_col'] => $timeout,
);
+
+ // WHERE
$where = array("{$this->_config['handle_col']} = ?" => $handle);
+
+ // perform update
$sql->update($this->_config['table'], $data, $where);
-
- // set cookie
- setcookie(
- $this->_config['cookie_name'],
+
+ // finally, set the cookie
+ $this->_setCookie(
"$identifier:$token",
- time() + $this->_config['cookie_expire'],
- $this->_config['cookie_path'],
- $this->_config['cookie_domain'],
- $this->_config['cookie_secure'],
- $this->_config['cookie_httponly']
+ time() + $this->_config['timeout']
);
- }
+ }
/**
*
@@ -300,18 +314,59 @@
*/
protected function _processLogout()
{
- // set cookie to some time in the past (one month ago)
+ // first, log us out
+ $status = parent::_processLogout();
+
+ // delete auth cookie
+ $this->_setCookie('DELETED', time());
+
+ // return status from parent
+ return $status;
+ }
+
+ /**
+ *
+ * Sets a cookie
+ *
+ * @param string $content Cookie content
+ *
+ * @param int $expire Expire time in seconds
+ *
+ * @return void
+ *
+ */
+ protected function _setCookie($content, $expire)
+ {
+ // set cookie
setcookie(
$this->_config['cookie_name'],
- 'DELETED',
- time() - 2592000,
+ $content,
+ $expire,
$this->_config['cookie_path'],
$this->_config['cookie_domain'],
$this->_config['cookie_secure'],
$this->_config['cookie_httponly']
);
+ }
+
+ /**
+ *
+ * Creates the identifier for a user handle
+ *
+ * @param string $handle User handle
+ *
+ * @return void
+ *
+ */
+ protected function _createIdentifier($handle)
+ {
+ // generate identifier
+ $identifier = hash(
+ $this->_config['hash_algo'],
+ $this->_config['salt']
+ . hash($this->_config['hash_algo'], $handle)
+ );
- // return status from parent
- return parent::_processLogout();
+ return $identifier;
}
}