294 lines
7.1 KiB
PHP
294 lines
7.1 KiB
PHP
<?php
|
|
|
|
namespace Birke\Rememberme;
|
|
|
|
class Authenticator
|
|
{
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $cookieName = "PHP_REMEMBERME";
|
|
|
|
/**
|
|
* @var Cookie
|
|
*/
|
|
protected $cookie;
|
|
|
|
/**
|
|
* @var Storage\StorageInterface
|
|
*/
|
|
protected $storage;
|
|
|
|
/**
|
|
* Number of seconds in the future the cookie and storage will expire (defaults to 1 week)
|
|
* @var int
|
|
*/
|
|
protected $expireTime = 604800;
|
|
|
|
/**
|
|
* If the return from the storage was Birke\Rememberme\Storage\StorageInterface::TRIPLET_INVALID,
|
|
* this is set to true
|
|
* @var bool
|
|
*/
|
|
protected $lastLoginTokenWasInvalid = false;
|
|
|
|
/**
|
|
* If the login token was invalid, delete all login tokens of this user
|
|
* @var bool
|
|
*/
|
|
protected $cleanStoredTokensOnInvalidResult = true;
|
|
|
|
/**
|
|
* Additional salt to add more entropy when the tokens are stored as hashes.
|
|
* @var string
|
|
*/
|
|
protected $salt = "";
|
|
|
|
/**
|
|
* @param Storage\StorageInterface $storage
|
|
*/
|
|
public function __construct(Storage\StorageInterface $storage)
|
|
{
|
|
$this->storage = $storage;
|
|
$this->cookie = new Cookie();
|
|
}
|
|
|
|
/**
|
|
* Check Credentials from cookie. Returns false if login was not successful, credential string if it was successful
|
|
* @return bool|string
|
|
*/
|
|
public function login()
|
|
{
|
|
$cookieValues = $this->getCookieValues();
|
|
|
|
if (!$cookieValues) {
|
|
return false;
|
|
}
|
|
|
|
$loginResult = false;
|
|
|
|
switch ($this->storage->findTriplet($cookieValues[0], $cookieValues[1] . $this->salt, $cookieValues[2] . $this->salt)) {
|
|
|
|
case Storage\StorageInterface::TRIPLET_FOUND:
|
|
$expire = time() + $this->expireTime;
|
|
$newToken = $this->createToken();
|
|
$this->storage->replaceTriplet($cookieValues[0], $newToken . $this->salt, $cookieValues[2] . $this->salt, $expire);
|
|
$this->cookie->setCookie($this->cookieName, implode("|", array($cookieValues[0], $newToken, $cookieValues[2])), $expire);
|
|
$loginResult = $cookieValues[0];
|
|
break;
|
|
|
|
case Storage\StorageInterface::TRIPLET_INVALID:
|
|
$this->cookie->setCookie($this->cookieName, "", time() - $this->expireTime);
|
|
$this->lastLoginTokenWasInvalid = true;
|
|
|
|
if ($this->cleanStoredTokensOnInvalidResult) {
|
|
$this->storage->cleanAllTriplets($cookieValues[0]);
|
|
}
|
|
|
|
break;
|
|
}
|
|
return $loginResult;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function cookieIsValid()
|
|
{
|
|
$cookieValues = $this->getCookieValues();
|
|
|
|
if (!$cookieValues) {
|
|
return false;
|
|
}
|
|
|
|
$state = $this->storage->findTriplet($cookieValues[0], $cookieValues[1] . $this->salt, $cookieValues[2] . $this->salt);
|
|
return $state == Storage\StorageInterface::TRIPLET_FOUND;
|
|
}
|
|
|
|
/**
|
|
* @param $credential
|
|
* @return $this
|
|
*/
|
|
public function createCookie($credential)
|
|
{
|
|
$newToken = $this->createToken();
|
|
$newPersistentToken = $this->createToken();
|
|
|
|
$expire = time() + $this->expireTime;
|
|
|
|
$this->storage->storeTriplet($credential, $newToken . $this->salt, $newPersistentToken . $this->salt, $expire);
|
|
$this->cookie->setCookie($this->cookieName, implode("|", array($credential, $newToken, $newPersistentToken)), $expire);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Expire the rememberme cookie, unset $_COOKIE[$this->cookieName] value and
|
|
* remove current login triplet from storage.
|
|
* @param boolean $clearFromStorage
|
|
* @return boolean
|
|
*/
|
|
public function clearCookie($clearFromStorage = true)
|
|
{
|
|
if (empty($_COOKIE[$this->cookieName])) {
|
|
return false;
|
|
}
|
|
|
|
$cookieValues = explode("|", $_COOKIE[$this->cookieName], 3);
|
|
|
|
$this->cookie->setCookie($this->cookieName, "", time() - $this->expireTime);
|
|
|
|
unset($_COOKIE[$this->cookieName]);
|
|
|
|
if (!$clearFromStorage) {
|
|
return true;
|
|
}
|
|
|
|
if (count($cookieValues) < 3) {
|
|
return false;
|
|
}
|
|
|
|
$this->storage->cleanTriplet($cookieValues[0], $cookieValues[2] . $this->salt);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getCookieName()
|
|
{
|
|
return $this->cookieName;
|
|
}
|
|
|
|
/**
|
|
* @param $name
|
|
* @return $this
|
|
*/
|
|
public function setCookieName($name)
|
|
{
|
|
$this->cookieName = $name;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param Cookie $cookie
|
|
* @return $this
|
|
*/
|
|
public function setCookie(Cookie $cookie)
|
|
{
|
|
$this->cookie = $cookie;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function loginTokenWasInvalid()
|
|
{
|
|
return $this->lastLoginTokenWasInvalid;
|
|
}
|
|
|
|
/**
|
|
* @return Cookie
|
|
*/
|
|
public function getCookie()
|
|
{
|
|
return $this->cookie;
|
|
}
|
|
|
|
/**
|
|
* @param $state
|
|
* @return Authenticator
|
|
*/
|
|
public function setCleanStoredTokensOnInvalidResult($state)
|
|
{
|
|
$this->cleanStoredTokensOnInvalidResult = $state;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function getCleanStoredTokensOnInvalidResult()
|
|
{
|
|
return $this->cleanStoredTokensOnInvalidResult;
|
|
}
|
|
|
|
/**
|
|
* Create a pseudo-random token.
|
|
*
|
|
* The token is pseudo-random. If you need better security, read from /dev/urandom
|
|
*/
|
|
protected function createToken()
|
|
{
|
|
return bin2hex(random_bytes(32));
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
protected function getCookieValues()
|
|
{
|
|
// Cookie was not sent with incoming request
|
|
if (empty($_COOKIE[$this->cookieName])) {
|
|
return array();
|
|
}
|
|
|
|
$cookieValues = explode("|", $_COOKIE[$this->cookieName], 3);
|
|
|
|
if (count($cookieValues) < 3) {
|
|
return array();
|
|
}
|
|
|
|
return $cookieValues;
|
|
}
|
|
|
|
/**
|
|
* Return how many seconds in the future that the cookie will expire
|
|
* @return int
|
|
*/
|
|
public function getExpireTime()
|
|
{
|
|
return $this->expireTime;
|
|
}
|
|
|
|
/**
|
|
* @param int $expireTime How many seconds in the future the cookie will expire
|
|
*
|
|
* Default is 604800 (1 week)
|
|
*
|
|
* @return Authenticator
|
|
*/
|
|
public function setExpireTime($expireTime)
|
|
{
|
|
$this->expireTime = $expireTime;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getSalt()
|
|
{
|
|
return $this->salt;
|
|
}
|
|
|
|
/**
|
|
* The salt is additional information that is added to the tokens to make
|
|
* them more unqiue and secure. The salt is not stored in the cookie and
|
|
* should not saved in the storage.
|
|
*
|
|
* For example, to bind a token to an IP address use $_SERVER['REMOTE_ADDR'].
|
|
* To bind a token to the browser (user agent), use $_SERVER['HTTP_USER_AGENT].
|
|
* You could also use a long random string that is uniqe to your application.
|
|
* @param string $salt
|
|
*/
|
|
public function setSalt($salt)
|
|
{
|
|
$this->salt = $salt;
|
|
}
|
|
}
|