- Fixed a bug within cookie get/set

- Added a secure hash to cookie.
This commit is contained in:
Dave M. 2020-01-23 13:45:19 -05:00
parent 3797b91f23
commit 56194034f8
4 changed files with 78 additions and 36 deletions

View File

@ -5,7 +5,6 @@ namespace Storage;
class Cookie { class Cookie {
protected array $options = [ protected array $options = [
'name' => '',
'expires' => 0, 'expires' => 0,
'path' => null, 'path' => null,
'domain' => null, 'domain' => null,
@ -14,11 +13,9 @@ class Cookie {
'samesite' => "Strict", 'samesite' => "Strict",
]; ];
protected array $options; protected ? string $secureHash;
protected string $secureHash; public function __construct(array $options, ? string $secureHash) {
public function __construct(array $options, string $secureHash) {
$this->options = $options; $this->options = $options;
$this->secureHash = $secureHash; $this->secureHash = $secureHash;
} }
@ -34,7 +31,7 @@ class Cookie {
* @param boolean HTTP only (requires PHP 5.2 or higher) * @param boolean HTTP only (requires PHP 5.2 or higher)
* @return boolean * @return boolean
*/ */
public static function set( public function set(
?string $name, ?string $name,
?string $value = null, ?string $value = null,
?int $expires = null, ?int $expires = null,
@ -49,7 +46,7 @@ class Cookie {
return false; return false;
} }
$expire = ($expire == 0) ? 0 : time() + (int) $expire; $expires = ( $expires === 0 ) ? 0 : time() + (int) $expires;
$_COOKIE[$name] = $value; $_COOKIE[$name] = $value;
@ -62,6 +59,10 @@ class Cookie {
'samesite' => $this->options['samesite'] ?? ( $samesite ?: "" ), 'samesite' => $this->options['samesite'] ?? ( $samesite ?: "" ),
]; ];
if ( $value ) {
$value = sha1($this->secureHash . $value . $this->secureHash) . "|$value";
}
return $raw ? setrawcookie($name, $value ?: "", $options) : setcookie($name, $value ?: "", $options); return $raw ? setrawcookie($name, $value ?: "", $options) : setcookie($name, $value ?: "", $options);
} }
@ -72,9 +73,28 @@ class Cookie {
* @param mixed default value * @param mixed default value
* @return string * @return string
*/ */
public static function get(string $key, $default = null) public function get(string $key, $default = null)
{ {
return array_key_exists($key, $_COOKIE) ? $_COOKIE[$key] : $default; if ( ! $this->has($key) ) {
return $default;
}
if ( $this->secureHash ) {
list($hash, $value) = explode('|', $_COOKIE[$key], 2);
if (! $this->isSecure($hash, $value)) {
throw new Exception\CookieInvalidSecureHashException();
}
return $value;
}
return $_COOKIE[$key];
}
public function has($key) : bool
{
return array_key_exists($key, $_COOKIE ?? []);
} }
/** /**
@ -84,33 +104,26 @@ class Cookie {
* @param string URL domain * @param string URL domain
* @return boolean * @return boolean
*/ */
public static function delete(string $name, ?string $path = null, ?string $domain = null) { public function delete(string $name, ?string $path = null, ?string $domain = null) {
if ( ! $this->__isset($name) ) { if ( ! $this->has($name) ) {
return false; return false;
} }
unset( $_COOKIE[$name] ); unset( $_COOKIE[$name] );
return static::set($name, '', -86400, $path ?: ( $this->options['path'] ?? "" ), $domain ?: ( $this->options['domain'] ?? "" ), $this->options['secure'] ?? false, $this->options['httponly'] ?? false); return $this->set($name, '', -86400, $path ?: ( $this->options['path'] ?? "" ), $domain ?: ( $this->options['domain'] ?? "" ), $this->options['secure'] ?? false, $this->options['httponly'] ?? false);
} }
public function __set($key, $value) public function isSecure($hash, $value) : bool
{ {
return static::set($key, $value); return sha1($this->secureHash . $value . $this->secureHash) === $hash;
} }
public function __get($key) public function __invoke(...$arguments)
{ {
return static::get($key); switch(count($arguments)) {
} case 1: return $this->get(...$arguments);
case 2: return $this->set(...$arguments);
public function __isset($key) }
{
return array_key_exists($key, $_SESSION);
}
public function __unset($key)
{
$this->delete($key);
} }
} }

View File

@ -0,0 +1,5 @@
<?php
namespace Stoarge\Exception;
class CookieInvalidSecureHashException extends \Exception {}

View File

@ -2,11 +2,15 @@
namespace Storage; namespace Storage;
use session_name, session_id, session_start, session_destroy, session_save_path,
session_regenerate_id, session_cache_limiter, session_get_cookie_params,
session_set_cookie_params, session_status, time, array_key_exists;
class Session class Session
{ {
public static function get($key, $default = null) public static function get($key, $default = null)
{ {
return array_key_exists($key, $_SESSION) ? $_SESSION[$key] : $default; return static::has($key) ? $_SESSION[$key] : $default;
} }
public static function set($key, $value) public static function set($key, $value)
@ -19,13 +23,20 @@ class Session
unset($_SESSION[$key]); unset($_SESSION[$key]);
} }
public static function clearAll() public static function has($key) : bool
{
return array_key_exists($key, $_SESSION ?? []);
}
public static function clearAll() : void
{ {
$_SESSION = []; $_SESSION = [];
} }
public static function start(array $options, Cookie $cookie) public static function start(array $options, Cookie $cookie, string $savePath) : void
{ {
session_save_path($savePath);
if ( session_status() === PHP_SESSION_ACTIVE ) { if ( session_status() === PHP_SESSION_ACTIVE ) {
return; return;
} }
@ -33,7 +44,7 @@ class Session
$params = session_get_cookie_params(); $params = session_get_cookie_params();
$params = [ $params = [
'expires' => $options['lifetime'] ?? $params['lifetime'], 'lifetime' => $options['lifetime'] ?? $params['lifetime'],
'path' => $options['path'] ?? $params['path'], 'path' => $options['path'] ?? $params['path'],
'domain' => $options['domain'] ?? $params['domain'], 'domain' => $options['domain'] ?? $params['domain'],
'secure' => $options['secure'] ?? $params['secure'], 'secure' => $options['secure'] ?? $params['secure'],
@ -53,7 +64,7 @@ class Session
session_start(); session_start();
# Reset timeout after session started # Reset timeout after session started
$cookie->set(session_name(), session_id(), time() + $params['expires'], $params['path'], $params['domain'], $params['secure'], $params['httponly'], $params['samesite']); $cookie->set(session_name(), session_id(), time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'], $params['samesite']);
} }
public static function regenerate() public static function regenerate()
@ -65,10 +76,10 @@ class Session
public static function destroy() public static function destroy()
{ {
$this->clearAll(); static::clearAll();
if ( ini_get("session.use_cookies") ) { if ( ini_get("session.use_cookies") ) {
$cookie->delete(session_name(), $params['path'] ?? "", $params['domain'] ?? ""); ( new Cookie([], null) )->delete(session_name(), $params['path'] ?? "", $params['domain'] ?? "");
} }
if ( session_status() === PHP_SESSION_ACTIVE ) { if ( session_status() === PHP_SESSION_ACTIVE ) {
@ -76,6 +87,14 @@ class Session
} }
} }
public function __invoke(...$arguments)
{
switch(count($arguments)) {
case 1: return static::get(...$arguments);
case 2: return static::set(...$arguments);
}
}
public function __set($key, $value) public function __set($key, $value)
{ {
return static::set($key, $value); return static::set($key, $value);

View File

@ -16,13 +16,15 @@ class SessionMiddleware implements MiddlewareInterface
'domain' => null, 'domain' => null,
'secure' => false, 'secure' => false,
'httponly' => true, 'httponly' => true,
'lifetime' => 28800, # A day of work ~ 7 hours 'lifetime' => 28800, # A day of work ~ 8 hours
'samesite' => "", 'samesite' => "",
]; ];
protected Cookie $cookie; protected Cookie $cookie;
public function __construct(Cookie $cookie, array $options = []) protected string $savePath;
public function __construct(Cookie $cookie, array $options = [], ?string $savePath = null)
{ {
$this->cookie = $cookie; $this->cookie = $cookie;
@ -32,6 +34,8 @@ class SessionMiddleware implements MiddlewareInterface
} }
$this->options = array_merge($this->options, $options); $this->options = array_merge($this->options, $options);
$this->savePath = $savePath ?? sys_get_temp_dir();
} }
/** /**
@ -43,7 +47,8 @@ class SessionMiddleware implements MiddlewareInterface
*/ */
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{ {
Session::start($this->options, $this->cookie); Session::start($this->options, $this->cookie, $this->savePath);
return $handler->handle($request); return $handler->handle($request);
} }
} }