From 3797b91f23eb7e4daafcc80d07b9cfa940f7b2f8 Mon Sep 17 00:00:00 2001 From: Dave Mc Nicoll Date: Mon, 18 Nov 2019 10:25:25 -0500 Subject: [PATCH] - First commit --- composer.json | 17 ++++++ src/Cookie.php | 116 ++++++++++++++++++++++++++++++++++++++ src/Session.php | 98 ++++++++++++++++++++++++++++++++ src/SessionMiddleware.php | 49 ++++++++++++++++ 4 files changed, 280 insertions(+) create mode 100644 composer.json create mode 100644 src/Cookie.php create mode 100644 src/Session.php create mode 100644 src/SessionMiddleware.php diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..cad2b1d --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "mcnd/storage", + "description": "A session and cookie handler which integrates itself as a middleware", + "keywords": ["storage","session","cookie","psr15","middleware"], + "license": "MIT", + "authors": [ + { + "name": "Dave Mc Nicoll", + "email": "dave.mcnicoll@cslsj.qc.ca" + } + ], + "autoload": { + "psr-4": { + "Storage\\": "src/" + } + } +} diff --git a/src/Cookie.php b/src/Cookie.php new file mode 100644 index 0000000..1bcf117 --- /dev/null +++ b/src/Cookie.php @@ -0,0 +1,116 @@ + '', + 'expires' => 0, + 'path' => null, + 'domain' => null, + 'secure' => false, + 'httponly' => true, + 'samesite' => "Strict", + ]; + + protected array $options; + + protected string $secureHash; + + public function __construct(array $options, string $secureHash) { + $this->options = $options; + $this->secureHash = $secureHash; + } + + /** + * Sets a cookie with the given parameters. + * @param string cookie name or array of config options + * @param string cookie value + * @param integer number of seconds before the cookie expires + * @param string URL path to allow + * @param string URL domain to allow + * @param boolean HTTPS only + * @param boolean HTTP only (requires PHP 5.2 or higher) + * @return boolean + */ + public static function set( + ?string $name, + ?string $value = null, + ?int $expires = null, + ?string $path = null, + ?string $domain = null, + ?bool $secure = null, + ?bool $httponly = null, + ?string $samesite = null, + ?bool $raw = false + ) { + if ( headers_sent() ) { + return false; + } + + $expire = ($expire == 0) ? 0 : time() + (int) $expire; + + $_COOKIE[$name] = $value; + + $options = [ + 'expires' => $this->options['expires'] ?? ( $expires ?: 0 ), + 'path' => $this->options['path'] ?? ( $path ?: "" ), + 'domain' => $this->options['domain'] ?? ( $domain ?: "" ), + 'secure' => $this->options['secure'] ?? ( $secure ?: false ), + 'httponly' => $this->options['httponly'] ?? ( $httponly ?: false ), + 'samesite' => $this->options['samesite'] ?? ( $samesite ?: "" ), + ]; + + return $raw ? setrawcookie($name, $value ?: "", $options) : setcookie($name, $value ?: "", $options); + } + + + /** + * Fetch a cookie value, using the Input library. + * @param string cookie name + * @param mixed default value + * @return string + */ + public static function get(string $key, $default = null) + { + return array_key_exists($key, $_COOKIE) ? $_COOKIE[$key] : $default; + } + + /** + * Nullify and unset a cookie. + * @param string cookie name + * @param string URL path + * @param string URL domain + * @return boolean + */ + public static function delete(string $name, ?string $path = null, ?string $domain = null) { + if ( ! $this->__isset($name) ) { + return false; + } + + unset( $_COOKIE[$name] ); + + return static::set($name, '', -86400, $path ?: ( $this->options['path'] ?? "" ), $domain ?: ( $this->options['domain'] ?? "" ), $this->options['secure'] ?? false, $this->options['httponly'] ?? false); + } + + public function __set($key, $value) + { + return static::set($key, $value); + } + + public function __get($key) + { + return static::get($key); + } + + public function __isset($key) + { + return array_key_exists($key, $_SESSION); + } + + public function __unset($key) + { + $this->delete($key); + } +} diff --git a/src/Session.php b/src/Session.php new file mode 100644 index 0000000..bba3b0a --- /dev/null +++ b/src/Session.php @@ -0,0 +1,98 @@ + $options['lifetime'] ?? $params['lifetime'], + 'path' => $options['path'] ?? $params['path'], + 'domain' => $options['domain'] ?? $params['domain'], + 'secure' => $options['secure'] ?? $params['secure'], + 'httponly' => $options['httponly'] ?? $params['httponly'], + 'samesite' => $options['samesite'] ?? $params['samesite'] ?? "", + ]; + + if ( version_compare(PHP_VERSION, '7.3.0') >= 0 ) { + session_set_cookie_params($params); + } + else { + session_set_cookie_params( ...array_values(array_slice($params, 0, 5)) ); + } + + session_name($options['name']); + session_cache_limiter($options['cache_limiter'] ?? 'nocache'); + session_start(); + + # Reset timeout after session started + $cookie->set(session_name(), session_id(), time() + $params['expires'], $params['path'], $params['domain'], $params['secure'], $params['httponly'], $params['samesite']); + } + + public static function regenerate() + { + if ( session_status() === PHP_SESSION_ACTIVE ) { + session_regenerate_id(true); + } + } + + public static function destroy() + { + $this->clearAll(); + + if ( ini_get("session.use_cookies") ) { + $cookie->delete(session_name(), $params['path'] ?? "", $params['domain'] ?? ""); + } + + if ( session_status() === PHP_SESSION_ACTIVE ) { + session_destroy(); + } + } + + public function __set($key, $value) + { + return static::set($key, $value); + } + + public function __get($key) + { + return static::get($key); + } + + public function __isset($key) + { + return array_key_exists($key, $_SESSION); + } + + public function __unset($key) + { + $this->delete($key); + } +} diff --git a/src/SessionMiddleware.php b/src/SessionMiddleware.php new file mode 100644 index 0000000..012f949 --- /dev/null +++ b/src/SessionMiddleware.php @@ -0,0 +1,49 @@ + '', + 'path' => null, + 'domain' => null, + 'secure' => false, + 'httponly' => true, + 'lifetime' => 28800, # A day of work ~ 7 hours + 'samesite' => "", + ]; + + protected Cookie $cookie; + + public function __construct(Cookie $cookie, array $options = []) + { + $this->cookie = $cookie; + + if ( empty($options['name']) ) { + # unique directory-based named session + $options['name'] = md5(__FILE__); + } + + $this->options = array_merge($this->options, $options); + } + + /** + * + * @param RequestInterface $request PSR7 request object + * @param RequestHandlerInterface $response PSR7 handler object + * + * @return ResponseInterface PSR7 response object + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + { + Session::start($this->options, $this->cookie); + return $handler->handle($request); + } +}