Skip to content

Instantly share code, notes, and snippets.

@subversivo58
Last active September 5, 2016 07:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save subversivo58/7ec04420a5b71c2017b0e0df8045b626 to your computer and use it in GitHub Desktop.
Save subversivo58/7ec04420a5b71c2017b0e0df8045b626 to your computer and use it in GitHub Desktop.
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);

*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment