Skip to content

Instantly share code, notes, and snippets.

@fitzagard
Created October 20, 2013 17:05
Show Gist options
  • Save fitzagard/7072287 to your computer and use it in GitHub Desktop.
Save fitzagard/7072287 to your computer and use it in GitHub Desktop.
Lithium MongoDB Session Extension
<?php
/**
* Lithium: the most rad php framework
*
* @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
* @license http://opensource.org/licenses/bsd-license.php The BSD License
*/
namespace app\extensions\adapter\session;
use lithium\core\Libraries;
use lithium\data\Connections;
use RuntimeException;
/**
* The `Model` adapter is a simple session adapter which allows session data to be written to a
* persistent storage that can be queried by a Lithium model.
* In order to use this adapter, you must first create a model that will be used to connect the
* adapter to.
*
* For example:
*
* {{{
* use \lithium\storage\Session;
*
* Session::config(array(
* 'default' => array('adapter' => 'Model', 'model' => 'Session')
* ));
* }}}
*
* This will cause your users' session handling to be taken care of by the normal
* model CRUD operations.
*/
class Model extends \lithium\core\Object {
/**
* Holds an instance of the record for the current session. Usually an instance of
* `lithium\data\entity\Record` or `lithium\data\entity\Document`.
*
* @var object
*/
protected $_data = null;
/**
* The fully namespaced class name which the adapter uses to read and write session data.
*
* @var string
*/
protected $_model = null;
/**
* Default ini settings for this session adapter
*
* @var array Keys are session ini settings, but without the `session.` namespace.
*/
protected $_defaults = array(
'cookie_secure' => false, 'cookie_httponly' => false
);
/**
* Sets up the adapter with the configuration assigned by the `Session` class.
*
* @param array $config Available configuration options for this adapter:
* - `'config'` _string_: The name of the model that this adapter should use.
*/
public function __construct(array $config = array()) {
$defaults = array('model' => null, 'expiry' => '+3 days');
parent::__construct($config + $defaults);
}
/**
* Initialization of the session.
* Sets the session save handlers to this adapters' corresponding methods.
*
* @return void
*/
protected function _init() {
parent::_init();
if (!$this->_config['model']) {
$message = "A valid model is required to use the Model session adapter.";
throw new RuntimeException($message);
}
foreach ($this->_defaults as $key => $config) {
if (isset($this->_config[$key])) {
if (ini_set("session.{$key}", $this->_config[$key]) === false) {
throw new RuntimeException("Could not initialize the session.");
}
}
}
$this->_model = Libraries::locate('models', $this->_config['model']);
session_set_save_handler(
array(&$this, '_open'), array(&$this, '_close'), array(&$this, '_read'),
array(&$this, '_write'), array(&$this, '_destroy'), array(&$this, '_gc')
);
register_shutdown_function('session_write_close');
$this->_startup();
}
/**
* Starts the session.
*
* @return boolean True if session successfully started (or has already been started),
* false otherwise.
*/
protected static function _startup() {
if (session_id() !== '') {
return true;
}
if (!isset($_SESSION)) {
session_cache_limiter("nocache");
}
return session_start();
}
/**
* Obtain the status of the session.
*
* @return boolean True if $_SESSION is accessible, false otherwise */
public function isStarted() {
return isset($_SESSION);
}
/**
* Uses PHP's default session handling to generate a unique session ID.
* This will be used as the primary key for all session-related operations.
*
* @return string Returns the session ID for the current request, or `null` if the session is
* invalid or if a key could not be generated.
*/
public function key() {
return session_id() ?: null;
}
/**
* Called when opening the session - the equivalent of a 'session constructor'.
* Creates & memoizes a Model record/document, on which future session operations will interact
* to reduce the number of roundtrip operations on the persistent storage engine.
*
* @param string $path Not used for this adapter.
* @param string $name Not used for this adapter.
* @return void
*/
public function _open($path, $name) {
$model = $this->_model;
$id = $this->key();
if (!$this->_data = $model::find($id)) {
$data = $id ? $model::key($id) : array();
$this->_data = $model::create($data);
$this->_data->expiry = strtotime($this->_config['expiry']);
}
}
/**
* Session save handler callback for session destruction - called when session_destroy()
* is invoked.
*
* @param string $id The session ID to be destroyed. This is not used explicitly - rather,
* the memoized DB record object's delete() method is called.
* @param return boolean True on successful destruction, false otherwise.
*/
public function _destroy($id) {
return $this->_data->delete();
}
/**
* Closes the session.
*
* @return boolean Always returns true.
*/
public function _close() {
unset($this->_data);
return true;
}
/**
* Delete all expired entries from the session.
*
* @param integer $lifetime Maximum valid session lifetime.
* @return boolean True on successful garbage collect, false otherwise.
*/
public function _gc($lifetime) {
$model = $this->_model;
$model::remove(array('expiry' => array('<=' => time() - $lifetime)));
}
/**
* Delete a value from the session.
*
* @param string $key The key of the data to be deleted.
* @param array $options Not implemented for this adapter method.
* @return boolean
*/
public function delete($key, array $options = array()) {
$_data = $this->_data;
return function($self, $params, $chain) use (&$_data) {
if ($_data->{$params['key']} !== null) {
$_data->{$params['key']} = null;
return true;
}
return false;
};
}
/**
* Read a value from the session.
*
* @param string $key The key of the data to be returned. If no key is specified,
* then all session data is returned in an array of key/value pairs.
* @param array $options Not implemented for this adapter method.
* @return mixed
*/
public function read($key = null, array $options = array()) {
$_this =& $this;
return function($self, $params, $chain) use (&$_this) {
return $_this->_read($params['key']);
};
}
/**
* The session save handler callback for reading data from the session.
*
* @param string $key The key of the data to be returned. If no key is specified,
* then all session data is returned in an array of key/value pairs.
* @return mixed Value corresponding to key if set, null otherwise.
*/
public function _read($key = null) {
if (!$this->_data || !is_object($this->_data)) {
return null;
}
if ($key === null) {
return $this->_data->data();
}
$data = $this->_data->{$key};
return ($data instanceof \lithium\data\Entity) ? $data->data() : $data;
}
/**
* Write a value to the session.
*
* @param string $key The key of the data to be returned.
* @param mixed $value The value to be written to the session.
* @param array $options Not implemented for this adapter method.
* @return boolean True if write was successful, false otherwise.
*/
public function write($key, $value = null, array $options = array()) {
$_data =& $this->_data;
return function($self, $params, $chain) use (&$_data) {
$_data->set(array($params['key'] => $params['value']));
return true;
};
}
/**
* The session save handler callback for writing data to the session.
*
* @param string $key The key of the data to be returned.
* @param mixed $value The value to be written to the session.
* @return boolean True if write was successful, false otherwise.
*/
public function _write($key, $value) {
if (!$this->_data || !is_object($this->_data)) {
return false;
}
$model = $this->_data->model();
$key = $model::key($key);
$expiry = strtotime($this->_config['expiry']);
return $this->_data->save($key + compact('expiry'));
}
/**
* Checks if a value has been set in the session.
*
* @param string $key Key of the entry to be checked.
* @return boolean True if the key exists, false otherwise.
*/
public function check($key) {
$_data = $this->_data;
return function($self, $params) use (&$_data) { return isset($_data->{$params['key']}); };
}
public static function enabled() {
return session_id() ?: null;
}
/**
* Clears all keys from the session.
*
* @param array $options Options array. Not used fro this adapter method.
* @return boolean True on successful clear, false otherwise.
*/
public function clear(array $options = array()) {
$_data = $this->_data;
return function($self, $params, $chain) use (&$_data) {
return $_data->delete();
};
}
}
?>
<?php
namespace app\models;
class MongoSession extends \lithium\data\Model {
}
?>
<?php
/**
* Lithium: the most rad php framework
*
* @copyright Copyright 2011, Union of RAD (http://union-of-rad.org)
* @license http://opensource.org/licenses/bsd-license.php The BSD License
*/
/**
* This configures your session storage. The Cookie storage adapter must be connected first, since
* it intercepts any writes where the `'expires'` key is set in the options array.
*/
use lithium\storage\Session;
Session::config(array(
'default' => array(
'adapter' => 'app\extensions\adapter\session\Model',
'model' => 'MongoSession',
)
));
/**
* Uncomment the lines below to enable forms-based authentication. This configuration will attempt
* to authenticate users against a `Users` model. In a controller, run
* `Auth::check('default', $this->request)` to authenticate a user. This will check the POST data of
* the request (`lithium\action\Request::$data`) to see if the fields match the `'fields'` key of
* the configuration below. If successful, it will write the data returned from `Users::first()` to
* the session using the default session configuration.
*
* Once the session data is written, you can call `Auth::check('default')` to check authentication
* status or retrieve the user's data from the session. Call `Auth::clear('default')` to remove the
* user's authentication details from the session. This effectively logs a user out of the system.
* To modify the form input that the adapter accepts, or how the configured model is queried, or how
* the data is stored in the session, see the `Form` adapter API or the `Auth` API, respectively.
*
* @see lithium\security\auth\adapter\Form
* @see lithium\action\Request::$data
* @see lithium\security\Auth
*/
use lithium\security\Auth;
Auth::config(array(
'userLogin' => array(
'adapter' => 'Form',
'model' => 'Users',
'fields' => array('username', 'password'),
'scope' => array('active' => true)
)
));
session_cache_limiter('public');
?>
@bengearig
Copy link

In the Model::_open($path, $name) method, the if statement should read if ($id === null || !$this->_data = $model::find($id)) { instead of if (!$this->_data = $model::find($id)) { otherwise a warning may be returned. I get this warning if using MongoDB and the model hasn't been queried yet and the session id is null. The extra check for a null session id skips the warning and un-needed method call and creates the session.

Is there any particular security concern that I am overlooking?

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