class HandlerSessionManager
{
private $dbCollection;
private $dbSession;
private $dbName;
private $expire;
private $token;
private $clean;
private $key;
public function __construct($options = false)
{
// define name of colection (set session_name() or default session_name())
$this->dbCollection = $options['collection'] ?? session_name();
// define database name
$this->dbName = ( $options[ 'databasename' ] ?? 'SessionManager' );
// define database for connection in MongoDB (if no have.. create.)
$this->dbSession = (new MongoDB\Client)->{$this->dbName};
// define expiration time session (get default or choose (options or default 1 hour))
$this->expire = get_cfg_var( "session.gc_maxlifetime" ) ?? ( $options['expire'] ?? 3600 );
// define token (for request session values in another fonts e.g: in nodejs)
$this->token = $options[ 'token' ] ?? false;
// define if use auto_garbage function for clean old sessions (default false)
$this->clean = $options[ 'force_garbage' ] ? true : false;
// define name of session
session_name($this->dbCollection);
// set save handler
session_set_save_handler(
[ $this, 'open' ],
[ $this, 'close' ],
[ $this, 'read' ],
[ $this, 'write' ],
[ $this, 'destroy' ],
[ $this, 'gc' ]
);
// shutdown
register_shutdown_function('session_write_close');
// check if session already started
if(!isset($_SESSION)){
session_start();
}
// auto call
self::auto_garbage();
}
public function open()
{
$this->key = $this->getKey('KEY_' . session_name());
if($this->dbSession){
return true;
}else{
return false;
}
}
public function close()
{
$this->dbSession = null;
return true;
}
public function read($id)
{
$doc = $this->dbSession->{$this->dbCollection}->findOne( ["_id" => $id] );
return is_object($doc) ? $this->decrypt($doc->sessionData, $this->key) : "";
}
public function write($id, $data)
{
try{
$encdata = $this->encrypt($data, $this->key);
$create = $this->dbSession->{$this->dbCollection}->updateOne( ["_id" => $id], ['$set' => ["_id" => $id, "token" => $this->token, "sessionData" => $encdata, "expire" => ( time() + $this->expire )] ], ["upsert" => true] );
return true;
}catch(Exception $e){
var_dump($e);
}
}
public function destroy($id)
{
$result = $this->dbSession->{$this->dbCollection}->findOneAndDelete( ["_id" => $id] );
if($result){
return true;
}else{
return false;
}
}
public function gc()
{
$result = $this->dbSession->{$this->dbCollection}->findOneAndDelete( ["expire" => ['$lt' => time()]] );
return true;
}
public function auto_garbage()
{
if($this->clean){
$this->dbSession->{$this->dbCollection}->findOneAndDelete( ["expire" => ['$lt' => time()]] );
}
}
/**
* Encrypt and authenticate
*
* @param string $data
* @param string $key
* @return string
*/
protected function encrypt($data, $key)
{
$iv = random_bytes(16); // AES block size in CBC mode
// Encryption
$ciphertext = openssl_encrypt(
$data,
'AES-256-CBC',
mb_substr($key, 0, 32, '8bit'),
OPENSSL_RAW_DATA,
$iv
);
// Authentication
$hmac = hash_hmac(
'SHA256',
$iv . $ciphertext,
mb_substr($key, 32, null, '8bit'),
true
);
return $hmac . $iv . $ciphertext;
}
/**
* Authenticate and decrypt
*
* @param string $data
* @param string $key
* @return string
*/
protected function decrypt($data, $key)
{
$hmac = mb_substr($data, 0, 32, '8bit');
$iv = mb_substr($data, 32, 16, '8bit');
$ciphertext = mb_substr($data, 48, null, '8bit');
// Authentication
$hmacNew = hash_hmac(
'SHA256',
$iv . $ciphertext,
mb_substr($key, 32, null, '8bit'),
true
);
if (! $this->hash_equals($hmac, $hmacNew)) {
throw new \RuntimeException('Authentication failed');
}
// Decrypt
return openssl_decrypt(
$ciphertext,
'AES-256-CBC',
mb_substr($key, 0, 32, '8bit'),
OPENSSL_RAW_DATA,
$iv
);
}
/**
* Get the encryption and authentication keys from cookie
*
* @param string $name
* @return string
*/
protected function getKey($name)
{
if (empty($_COOKIE[$name])) {
$key = random_bytes(64); // 32 for encryption and 32 for authentication
$cookieParam = session_get_cookie_params();
setcookie(
$name,
base64_encode($key),
// if session cookie lifetime > 0 then add to current time
// otherwise leave it as zero, honoring zero's special meaning
// expire at browser close.
($cookieParam['lifetime'] > 0) ? time() + $cookieParam['lifetime'] : 0,
$cookieParam['path'],
$cookieParam['domain'],
$cookieParam['secure'],
$cookieParam['httponly']
);
} else {
$key = base64_decode($_COOKIE[$name]);
}
return $key;
}
/**
* Hash equals function for PHP 5.5+
*
* @param string $expected
* @param string $actual
* @return bool
*/
protected function hash_equals($expected, $actual)
{
$expected = (string) $expected;
$actual = (string) $actual;
if (function_exists('hash_equals')) {
return hash_equals($expected, $actual);
}
$lenExpected = mb_strlen($expected, '8bit');
$lenActual = mb_strlen($actual, '8bit');
$len = min($lenExpected, $lenActual);
$result = 0;
for ($i = 0; $i < $len; $i++) {
$result |= ord($expected[$i]) ^ ord($actual[$i]);
}
$result |= $lenExpected ^ $lenActual;
return ($result === 0);
}
public function __destruct()
{
session_write_close();
}
}
/** USAGE:
$options = [
'databasename' => 'PHPSESSIONS',
'collection' => 'PHPSESSID',
'expire' => 1440, // it's don't is majority set. It is optional case php.ini no have value set
'token' => 'new', // 'YOUR_SUPER_ENCRYPTED_TOKEN'
'force_garbage' => true
];
// init class
new HandlerSessionManager($options);
*/
Last active
September 5, 2016 07:33
-
-
Save subversivo58/7ec04420a5b71c2017b0e0df8045b626 to your computer and use it in GitHub Desktop.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment