summaryrefslogtreecommitdiffstats
path: root/src/Psecio/Gatekeeper
diff options
context:
space:
mode:
authorChris Cornutt <chris.cornutt@hp.com>2015-02-06 15:46:53 -0600
committerChris Cornutt <chris.cornutt@hp.com>2015-02-06 15:46:53 -0600
commitec562482c0b0e3c8637fea64270c4bbf74fe9fcf (patch)
tree7c2b7c3cd36cf7edfce14c396ab41d0cc486b2a5 /src/Psecio/Gatekeeper
parent47c50e6e4a0ab77f62d920dc14ec7b0ce2762ef8 (diff)
parent51ebe65e9b34f559b7c0e8c606fa227e7dfbe940 (diff)
downloadgatekeeper-2.1.zip
gatekeeper-2.1.tar.gz
gatekeeper-2.1.tar.bz2
Merge branch 'auth-token'2.1
Diffstat (limited to 'src/Psecio/Gatekeeper')
-rw-r--r--src/Psecio/Gatekeeper/AuthTokenModel.php63
-rw-r--r--src/Psecio/Gatekeeper/Gatekeeper.php37
-rw-r--r--src/Psecio/Gatekeeper/Session/RememberMe.php263
-rw-r--r--src/Psecio/Gatekeeper/UserModel.php13
4 files changed, 374 insertions, 2 deletions
diff --git a/src/Psecio/Gatekeeper/AuthTokenModel.php b/src/Psecio/Gatekeeper/AuthTokenModel.php
new file mode 100644
index 0000000..59bb41e
--- /dev/null
+++ b/src/Psecio/Gatekeeper/AuthTokenModel.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Psecio\Gatekeeper;
+
+class AuthTokenModel extends \Psecio\Gatekeeper\Model\Mysql
+{
+ /**
+ * Database table name
+ * @var string
+ */
+ protected $tableName = 'auth_tokens';
+
+ /**
+ * Model properties
+ * @var array
+ */
+ protected $properties = array(
+ 'id' => array(
+ 'description' => 'Token ID',
+ 'column' => 'id',
+ 'type' => 'integer'
+ ),
+ 'token' => array(
+ 'description' => 'Token value',
+ 'column' => 'token',
+ 'type' => 'varchar'
+ ),
+ 'verifier' => array(
+ 'description' => 'Verifier value',
+ 'column' => 'verifier',
+ 'type' => 'varchar'
+ ),
+ 'userId' => array(
+ 'description' => 'User ID',
+ 'column' => 'user_id',
+ 'type' => 'integer'
+ ),
+ 'user' => array(
+ 'description' => 'User related to token',
+ 'type' => 'relation',
+ 'relation' => array(
+ 'model' => '\\Psecio\\Gatekeeper\\UserModel',
+ 'method' => 'findByUserId',
+ 'local' => 'userId'
+ )
+ ),
+ 'expires' => array(
+ 'description' => 'Date Token Expires',
+ 'column' => 'expires',
+ 'type' => 'datetime'
+ ),
+ 'created' => array(
+ 'description' => 'Date Created',
+ 'column' => 'created',
+ 'type' => 'datetime'
+ ),
+ 'updated' => array(
+ 'description' => 'Date Updated',
+ 'column' => 'updated',
+ 'type' => 'datetime'
+ ),
+ );
+} \ No newline at end of file
diff --git a/src/Psecio/Gatekeeper/Gatekeeper.php b/src/Psecio/Gatekeeper/Gatekeeper.php
index a2fed7e..eb1f65b 100644
--- a/src/Psecio/Gatekeeper/Gatekeeper.php
+++ b/src/Psecio/Gatekeeper/Gatekeeper.php
@@ -177,10 +177,10 @@ class Gatekeeper
* Authenticate a user given the username/password credentials
*
* @param array $credentials Credential information (must include "username" and "password")
- * @param array $config Configuration options [optional]
+ * @param boolean $rememeber Flag to activate the "remember me" functionality
* @return boolean Pass/fail of authentication
*/
- public static function authenticate(array $credentials, array $config = array())
+ public static function authenticate(array $credentials, $remember = false)
{
$username = $credentials['username'];
$user = new UserModel(self::$datasource);
@@ -213,6 +213,10 @@ class Gatekeeper
if (self::$throttleStatus === true && $result === true) {
$instance->model->allow();
+
+ if ($remember === true) {
+ self::rememberMe($user);
+ }
}
return $result;
@@ -483,4 +487,33 @@ class Gatekeeper
$instance = new $classNs($config);
self::$restrictions[] = $instance;
}
+
+ /**
+ * Enable and set up the "Remember Me" cookie token handling for the given user
+ *
+ * @param \Psecio\Gatekeeper\UserModel|string $user User model instance
+ * @param array $config Set of configuration settings
+ * @return boolean Success/fail of sesssion setup
+ */
+ public static function rememberMe($user, array $config = array())
+ {
+ if (is_string($user)) {
+ $user = Gatekeeper::findUserByUsername($user);
+ }
+
+ $data = array_merge($_COOKIE, $config);
+ $remember = new Session\RememberMe(self::$datasource, $data, $user);
+ return $remember->setup();
+ }
+
+ /**
+ * Check the "Remember Me" token information (if it exists)
+ *
+ * @return boolean|\Psecio\Gatekeeper\UserModel Success/fail of token validation or User model instance
+ */
+ public static function checkRememberMe()
+ {
+ $remember = new Session\RememberMe(self::$datasource, $_COOKIE);
+ return $remember->verify();
+ }
} \ No newline at end of file
diff --git a/src/Psecio/Gatekeeper/Session/RememberMe.php b/src/Psecio/Gatekeeper/Session/RememberMe.php
new file mode 100644
index 0000000..844dc3e
--- /dev/null
+++ b/src/Psecio/Gatekeeper/Session/RememberMe.php
@@ -0,0 +1,263 @@
+<?php
+
+namespace Psecio\Gatekeeper\Session;
+
+class RememberMe
+{
+ /**
+ * Token name
+ * @var string
+ */
+ private $tokenName = 'gktoken';
+
+ /**
+ * Default expiration time
+ * @var string
+ */
+ private $expireInterval = '+14 days';
+
+ /**
+ * Data (cookie) for use in token evaluation
+ * @var array
+ */
+ private $data = array();
+
+ /**
+ * User instance to check against
+ * @var \Psecio\Gatekeeper\UserModel
+ */
+ private $user;
+
+ /**
+ * Datasource for use in making find//save requests
+ * @var \Psecio\Gatekeeper\DataSource
+ */
+ private $datasource;
+
+ /**
+ * Init the object and set up the datasource, data and possibly a user
+ *
+ * @param \Psecio\Gatekeeper\DataSource $datasource Data source to use for operations
+ * @param array $data Data to use in evaluation
+ * @param \Psecio\Gatekeeper\UserModel|null $user User model instance [optional]
+ */
+ public function __construct(\Psecio\Gatekeeper\DataSource $datasource, array $data, \Psecio\Gatekeeper\UserModel $user = null)
+ {
+ $this->datasource = $datasource;
+
+ if (!empty($data)) {
+ $this->data = $data;
+ }
+ if ($user !== null) {
+ $this->user = $user;
+ }
+ if (isset($this->data['interval'])) {
+ $this->expireInterval = $this->data['interval'];
+ }
+ }
+
+ /**
+ * Setup the "remember me" session and cookies
+ *
+ * @param \Psecio\Gatekeeper\UserModel|null $user User model instance [optional]
+ * @return boolean Success/fail of setting up the session/cookies
+ */
+ public function setup(\Psecio\Gatekeeper\UserModel $user = null)
+ {
+ $user = ($user === null) ? $this->user : $user;
+ $userToken = $this->getUserToken($user);
+
+ if ($userToken->id !== null || $this->isExpired($userToken)) {
+ return false;
+ }
+ $token = $this->generateToken();
+ $tokenModel = $this->saveToken($token, $user);
+ if ($tokenModel === false) {
+ return false;
+ }
+ $this->setCookies($tokenModel, $token);
+
+ return true;
+ }
+
+ /**
+ * Verify the token if it exists
+ * Removes the old token and sets up a new one if valid
+ *
+ * @param string $token Token value
+ * @param string $auth Auth value
+ * @return boolean Pass/fail result of the validation
+ */
+ public function verify($token = null, $auth = null)
+ {
+ // See if we have our cookies
+ $domain = $_SERVER['HTTP_HOST'];
+ $https = (isset($_SERVER['HTTPS'])) ? true : false;
+
+ if (!isset($this->data[$this->tokenName])) {
+ return false;
+ }
+
+ $tokenParts = explode(':', $this->data[$this->tokenName]);
+ $token = $this->getById($tokenParts[0]);
+ if ($token === false) {
+ return false;
+ }
+
+ $user = $token->user;
+ $userToken = $token->token;
+
+ // Remove the token (a new one will be made later)
+ $this->datasource->delete($token);
+
+ if ($this->hash_equals($this->data[$this->tokenName], $token->id.':'.hash('sha256', $userToken)) === false) {
+ return false;
+ }
+
+ $this->setup($user);
+ return $user;
+ }
+
+ /**
+ * Get the token information searching on given token string
+ *
+ * @param string $tokenValue Token string for search
+ * @return boolean|\Psecio\Gatekeeper\AuthTokenModel Instance if no query errors
+ */
+ public function getByToken($tokenValue)
+ {
+ $token = new \Psecio\Gatekeeper\AuthTokenModel($this->datasource);
+ $result = $this->datasource->find($token, array('token' => $tokenValue));
+ return $result;
+ }
+
+ /**
+ * Get a token by its unique ID
+ *
+ * @param integer $tokenId Token ID
+ * @return boolean|\Psecio\Gatekeeper\AuthTokenModel instance
+ */
+ public function getById($tokenId)
+ {
+ $token = new \Psecio\Gatekeeper\AuthTokenModel($this->datasource);
+ $result = $this->datasource->find($token, array('id' => $tokenId));
+ return $result;
+ }
+
+ /**
+ * Get the token by user ID
+ * Also performs evaluation to check if token is expired, returns false if so
+ *
+ * @param \Psecio\Gatekeeper\UserModel $user User model instance
+ * @return boolean|\Psecio\Gatekeeper\AuthTokenModel instance
+ */
+ public function getUserToken(\Psecio\Gatekeeper\UserModel $user)
+ {
+ $tokenModel = new \Psecio\Gatekeeper\AuthTokenModel($this->datasource);
+ return $this->datasource->find($tokenModel, array('userId' => $user->id));
+ }
+
+ /**
+ * Check to see if the token has expired
+ *
+ * @param \Psecio\Gatekeeper\AuthTokenModel $token Token model instance
+ * @param boolean $delete Delete/don't delete the token if expired [optional]
+ * @return boolean Token expired/not expired
+ */
+ public function isExpired(\Psecio\Gatekeeper\AuthTokenModel $token, $delete = true)
+ {
+ if (new \Datetime() > new \DateTime($token->expires)) {
+ if ($delete === true) {
+ $this->deleteToken($token->token);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Save the new token to the data source
+ *
+ * @param string $token Token string
+ * @param \Psecio\Gatekeeper\UserModel $user User model instance
+ * @return boolean|\Psecio\Gatekeeper\AuthTokenModel Success/fail of token creation or AuthTokenModel instance
+ */
+ public function saveToken($token, \Psecio\Gatekeeper\UserModel $user)
+ {
+ $expires = new \DateTime($this->expireInterval);
+ $tokenModel = new \Psecio\Gatekeeper\AuthTokenModel($this->datasource, array(
+ 'token' => $token,
+ 'userId' => $user->id,
+ 'expires' => $expires->format('Y-m-d H:i:s')
+ ));
+ $result = $this->datasource->save($tokenModel);
+ return ($result === false) ? false : $tokenModel;
+ }
+
+ /**
+ * Delete the token by token string
+ *
+ * @param string $token Token hash string
+ * @return boolean Success/fail of token record deletion
+ */
+ public function deleteToken($token)
+ {
+ $tokenModel = new \Psecio\Gatekeeper\AuthTokenModel($this->datasource);
+ $token = $this->datasource->find($tokenModel, array('token' => $token));
+ if ($token !== false) {
+ return $this->datasource->delete($token);
+ }
+ return false;
+ }
+
+ /**
+ * Generate the token value
+ *
+ * @return array Set of two token values (main and auth)
+ */
+ public function generateToken()
+ {
+ $factory = new \RandomLib\Factory;
+ $generator = $factory->getMediumStrengthGenerator();
+
+ return base64_encode($generator->generate(24));
+ }
+
+ /**
+ * Set the cookies with the main and auth tokens
+ *
+ * @param \Psecio\Gatekeeper\AuthTokenModel $tokenModel Auth token model instance
+ * @param string $token Token hash
+ * @param boolean $https Enable/disable HTTPS setting on cookies [optional]
+ * @param string $domain Domain value to set cookies on
+ */
+ public function setCookies(\Psecio\Gatekeeper\AuthTokenModel $tokenModel, $token, $https = false, $domain = null)
+ {
+ if ($domain === null && isset($_SERVER['HTTP_HOST'])) {
+ $domain = $_SERVER['HTTP_HOST'];
+ }
+
+ $tokenValue = $tokenModel->id.':'.hash('sha256', $token);
+ $expires = new \DateTime($this->expireInterval);
+ return setcookie($this->tokenName, $tokenValue, $expires->format('U'), '/', $domain, $https, true);
+ }
+
+ /**
+ * Polyfill PHP 5.6.0's hash_equals() feature
+ */
+ public function hash_equals($a, $b)
+ {
+ if (\function_exists('hash_equals')) {
+ return \hash_equals($a, $b);
+ }
+ if (\strlen($a) !== \strlen($b)) {
+ return false;
+ }
+ $res = 0;
+ $len = \strlen($a);
+ for ($i = 0; $i < $len; ++$i) {
+ $res |= \ord($a[$i]) ^ \ord($b[$i]);
+ }
+ return $res === 0;
+ }
+} \ No newline at end of file
diff --git a/src/Psecio/Gatekeeper/UserModel.php b/src/Psecio/Gatekeeper/UserModel.php
index ac5c811..8b7b346 100644
--- a/src/Psecio/Gatekeeper/UserModel.php
+++ b/src/Psecio/Gatekeeper/UserModel.php
@@ -141,6 +141,19 @@ class UserModel extends \Psecio\Gatekeeper\Model\Mysql
}
/**
+ * Find a user by their given ID
+ *
+ * @param integer $userId User ID
+ * @return boolean Success/fail of find operation
+ */
+ public function findByUserId($userId)
+ {
+ return $this->getDb()->find(
+ $this, array('id' => $userId)
+ );
+ }
+
+ /**
* Attach a permission to a user account
*
* @param integer|PermissionModel $perm Permission ID or model isntance