From 11d295eb0cf215a6edf15890d6f81fc726556264 Mon Sep 17 00:00:00 2001 From: phpnut Date: Mon, 12 Nov 2007 01:36:20 +0000 Subject: [PATCH] Fixes #3507 Session Security.level "high", session destroyed on media 404. Added ability to turn off HTTP_USER_AGENT check in a Controller::beforeFilter(), Added id() to Session helper and component to return current Session id, the component accepts a $id parameter to force setting the Session id which must be called in a Controller::beforeFilter(). Sessions id are not longer renewed if a request is from Ajax, or from requestAction(); When Security.level (1.2) or CAKE_SECURITY (1.1) is set the 'high' renewing of Session id only happens if request is 2 seconds after the last request. Added $_Session[Config][timeout] which forces renewing Session if request are within the 2 second limit and over 10 request. If an application is expected to make multiple request (more than 10) to the server in a single proccess, Configure::write('Security.level', 'medium'); (1.2) or $this->Session->security = 'medium'; (1.1) should be used in a beforeFilter for the specific methods. 1.2 Sessions allow using CacheEngines to store Sessions, be aware that using memory caching as the only storage of Sessions is not reliable. Further work will be done to allow using the CacheEngines with database Sessions, etc. git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@5982 3807eeeb-6ff5-0310-8944-8be069107fe0 --- cake/libs/cache.php | 15 +- cake/libs/controller/component.php | 5 +- cake/libs/controller/components/session.php | 96 ++++++++++--- cake/libs/session.php | 150 ++++++++++++++------ cake/libs/view/helpers/session.php | 17 ++- cake/tests/cases/libs/session.test.php | 2 + 6 files changed, 218 insertions(+), 67 deletions(-) diff --git a/cake/libs/cache.php b/cake/libs/cache.php index 414a9d7b8..47ecba248 100644 --- a/cake/libs/cache.php +++ b/cake/libs/cache.php @@ -166,6 +166,19 @@ class Cache extends Object { $_this->_Engine[$name] = null; return false; } +/** + * Garbage collection + * + * Permanently remove all expired and deleted data + * + * @access public + */ + function gc() { + $_this =& Cache::getInstance(); + $config = $_this->config(); + extract($config); + $_this->_Engine[$engine]->gc(); + } /** * Write data for key into cache * @@ -419,4 +432,4 @@ class CacheEngine extends Object { return $this->settings; } } -?> \ No newline at end of file +?> diff --git a/cake/libs/controller/component.php b/cake/libs/controller/component.php index 9af7a1ff2..efff8341a 100644 --- a/cake/libs/controller/component.php +++ b/cake/libs/controller/component.php @@ -62,7 +62,7 @@ class Component extends Object { $this->controller =& $controller; if ($this->controller->components !== false) { $loaded = array(); - $this->controller->components = array_merge($this->controller->components, array('Session')); + $this->controller->components = array_merge(array('Session'), $this->controller->components); $loaded = $this->_loadComponents($loaded, $this->controller->components); foreach (array_keys($loaded) as $component) { @@ -87,8 +87,6 @@ class Component extends Object { * @access protected */ function &_loadComponents(&$loaded, $components) { - $components[] = 'Session'; - foreach ($components as $component) { $parts = preg_split('/\/|\./', $component); @@ -139,5 +137,4 @@ class Component extends Object { return $loaded; } } - ?> \ No newline at end of file diff --git a/cake/libs/controller/components/session.php b/cake/libs/controller/components/session.php index 8324a377c..3fa013b9f 100644 --- a/cake/libs/controller/components/session.php +++ b/cake/libs/controller/components/session.php @@ -1,6 +1,5 @@ __bare = $controller->params['bare']; + } +/** + * Startup method. + * + * @param object $controller Instantiating controller + * @access public + */ + function startup(&$controller) { + if ($this->__started = false) { + $this->__start(); + } + } +/** + * Starts Session on if 'Session.start' is set to false in core.php * * @param string $base The base path for the Session * @access public @@ -70,21 +103,6 @@ class SessionComponent extends CakeSession { parent::__construct($base); $this->__active = true; } -/** - * Startup method. Copies controller data locally for rendering flash messages. - * - * @param object $controller Instantiating controller - * @access public - */ - function startup(&$controller) { - $this->base = $controller->base; - $this->webroot = $controller->webroot; - $this->here = $controller->here; - $this->params = $controller->params; - $this->action = $controller->action; - $this->data = $controller->data; - $this->plugin = $controller->plugin; - } /** * Used to write a value to a session key. * @@ -97,6 +115,7 @@ class SessionComponent extends CakeSession { */ function write($name, $value = null) { if ($this->__active === true) { + $this->__start(); if (is_array($name)) { foreach ($name as $key => $value) { if (parent::write($key, $value) === false) { @@ -124,6 +143,7 @@ class SessionComponent extends CakeSession { */ function read($name = null) { if ($this->__active === true) { + $this->__start(); return parent::read($name); } return false; @@ -139,6 +159,7 @@ class SessionComponent extends CakeSession { */ function del($name) { if ($this->__active === true) { + $this->__start(); return parent::del($name); } return false; @@ -149,11 +170,12 @@ class SessionComponent extends CakeSession { * In your controller: $this->Session->delete('Controller.sessKey'); * * @param string $name the name of the session key you want to delete - * @return bool, true is session variable is set and can be deleted, false is variable was not set. + * @return boolean true is session variable is set and can be deleted, false is variable was not set. * @access public */ function delete($name) { if ($this->__active === true) { + $this->__start(); return $this->del($name); } return false; @@ -169,6 +191,7 @@ class SessionComponent extends CakeSession { */ function check($name) { if ($this->__active === true) { + $this->__start(); return parent::check($name); } return false; @@ -183,6 +206,7 @@ class SessionComponent extends CakeSession { */ function error() { if ($this->__active === true) { + $this->__start(); return parent::error(); } return false; @@ -202,6 +226,7 @@ class SessionComponent extends CakeSession { */ function setFlash($message, $layout = 'default', $params = array(), $key = 'flash') { if ($this->__active === true) { + $this->__start(); $this->write('Message.' . $key, compact('message', 'layout', 'params')); } } @@ -214,6 +239,7 @@ class SessionComponent extends CakeSession { */ function renew() { if ($this->__active === true) { + $this->__start(); parent::renew(); } } @@ -227,6 +253,7 @@ class SessionComponent extends CakeSession { */ function valid() { if ($this->__active === true) { + $this->__start(); return parent::valid(); } return false; @@ -240,8 +267,41 @@ class SessionComponent extends CakeSession { */ function destroy() { if ($this->__active === true) { + $this->__start(); parent::destroy(); } } +/** + * Returns Session id + * + * If $id is passed in a beforeFilter, the Session will be started + * with the specified id + * + * @param $id string + * @return string + * @access public + */ + function id($id = null) { + return parent::id($id); + } +/** + * Starts Session if SessionComponent is used in Controller::beforeFilter(), + * or is called from + * + * @access private + */ + function __start(){ + if ($this->__started === false) { + if ($this->__bare === 0) { + if (!$this->id() && parent::start()) { + parent::_checkValid(); + $this->__started = true; + } else { + $this->__started = parent::start(); + } + } + } + return $this->__started; + } } ?> diff --git a/cake/libs/session.php b/cake/libs/session.php index 934db64d6..782f384d4 100644 --- a/cake/libs/session.php +++ b/cake/libs/session.php @@ -107,6 +107,13 @@ class CakeSession extends Object { * @access public */ var $watchKeys = array(); +/** + * Current Session id + * + * @var string + * @access public + */ + var $id = null; /** * Constructor. * @@ -145,17 +152,24 @@ class CakeSession extends Object { $this->sessionTime = $this->time + (Security::inactiveMins() * Configure::read('Session.timeout')); $this->security = Configure::read('Security.level'); - - if (function_exists('session_write_close')) { - session_write_close(); - } - - $this->__initSession(); - $this->__startSession(); - $this->__checkValid(); } parent::__construct(); } +/** + * Starts the Session. + * + * @param string $name Variable name to check for + * @return boolean True if variable is there + * @access public + */ + function start() { + if (function_exists('session_write_close')) { + session_write_close(); + } + $this->__initSession(); + $this->__startSession(); + return true; + } /** * Returns true if given variable is set in session. * @@ -171,7 +185,23 @@ class CakeSession extends Object { $result = Set::extract($_SESSION, $var); return isset($result); } - +/** + * + * @param id $name string + * @return string Session id + * @access public + */ + function id($id = null) { + if ($id) { + $this->id = $id; + session_id($this->id); + } + if (isset($_SESSION)) { + return session_id(); + } else { + return $this->id; + } + } /** * Temp method until we are able to remove the last eval(). * Builds an expression to fetch a session variable with specified name. @@ -223,9 +253,11 @@ class CakeSession extends Object { * @access private */ function __overwrite(&$old, $new) { - foreach ($old as $key => $var) { - if (!isset($new[$key])) { - unset($old[$key]); + if(!empty($old)) { + foreach ($old as $key => $var) { + if (!isset($new[$key])) { + unset($old[$key]); + } } } foreach ($new as $key => $var) { @@ -465,9 +497,29 @@ class CakeSession extends Object { } } break; + case 'cache': + if (!isset($_SESSION)) { + uses('Cache'); + if (function_exists('ini_set')) { + ini_set('session.use_trans_sid', 0); + ini_set('url_rewriter.tags', ''); + ini_set('session.save_handler', 'user'); + ini_set('session.use_cookies', 1); + ini_set('session.name', Configure::read('Session.cookie')); + ini_set('session.cookie_lifetime', $this->cookieLifeTime); + ini_set('session.cookie_path', $this->path); + } + } + session_set_save_handler(array('CakeSession','__open'), + array('CakeSession', '__close'), + array('Cache', 'read'), + array('Cache', 'write'), + array('Cache', 'delete'), + array('CakeSession', '__gc')); + break; default: if (!isset($_SESSION)) { - $config = CONFIGS . Configure::read('Session.cookie') . '.php'; + $config = CONFIGS . Configure::read('Session.save') . '.php'; if (is_file($config)) { require_once ($config); @@ -489,21 +541,30 @@ class CakeSession extends Object { } else { session_cache_limiter ("must-revalidate"); session_start(); - if (Configure::read('Security.level') === 'high') { - $this->renew(); - } header ('P3P: CP="NOI ADM DEV PSAi COM NAV OUR OTRo STP IND DEM"'); } } /** * Helper method to create a new session. * - * @access private + * @access protected */ - function __checkValid() { + function _checkValid() { if ($this->read('Config')) { if (Configure::read('Session.checkAgent') === false || $this->_userAgent == $this->read("Config.userAgent") && $this->time <= $this->read("Config.time")) { + $time = $this->read("Config.time"); $this->write("Config.time", $this->sessionTime); + + if (Configure::read('Security.level') === 'high') { + $check = $this->read("Config.timeout"); + $check = $check - 1; + $this->write("Config.timeout", $check); + + if (time() > ($time - (Security::inactiveMins() * Configure::read('Session.timeout')) + 2) || $check < 1) { + $this->renew(); + $this->write('Config.timeout', 10); + } + } $this->valid = true; } else { $this->destroy(); @@ -515,6 +576,7 @@ class CakeSession extends Object { $this->write("Config.userAgent", $this->_userAgent); $this->write("Config.time", $this->sessionTime); $this->write('Config.rand', rand()); + $this->write('Config.timeout', 10); $this->valid = true; $this->__setError(1, "Session is valid"); } @@ -526,29 +588,30 @@ class CakeSession extends Object { */ function __regenerateId() { $oldSessionId = session_id(); - $sessionpath = session_save_path(); - if (empty($sessionpath)) { - $sessionpath = "/tmp"; - } + if ($oldSessionId) { + $sessionpath = session_save_path(); + if (empty($sessionpath)) { + $sessionpath = "/tmp"; + } + if (isset($_COOKIE[session_name()])) { + setcookie(Configure::read('Session.cookie'), '', time() - 42000, $this->path); + } + session_regenerate_id(); + $newSessid = session_id(); - if (isset($_COOKIE[session_name()])) { - setcookie(Configure::read('Session.cookie'), '', time() - 42000, $this->path); + if (function_exists('session_write_close')) { + session_write_close(); + } + $this->__initSession(); + session_id($oldSessionId); + session_start(); + session_destroy(); + $file = $sessionpath . DS . "sess_$oldSessionId"; + @unlink($file); + $this->__initSession(); + session_id($newSessid); + session_start(); } - session_regenerate_id(); - $newSessid = session_id(); - - if (function_exists('session_write_close')) { - session_write_close(); - } - $this->__initSession(); - session_id($oldSessionId); - session_start(); - session_destroy(); - $file = $sessionpath . DS . "sess_$oldSessionId"; - @unlink($file); - $this->__initSession(); - session_id ($newSessid); - session_start(); } /** * Restarts this session. @@ -605,7 +668,14 @@ class CakeSession extends Object { function __close() { $probability = mt_rand(1, 150); if ($probability <= 3) { - CakeSession::__gc(); + switch(Configure::read('Session.save')) { + case 'cache': + Cache::gc(); + break; + default: + CakeSession::__gc(); + break; + } } return true; } diff --git a/cake/libs/view/helpers/session.php b/cake/libs/view/helpers/session.php index 7ee93b878..41733e7a9 100644 --- a/cake/libs/view/helpers/session.php +++ b/cake/libs/view/helpers/session.php @@ -94,7 +94,7 @@ class SessionHelper extends CakeSession { * In your view: $session->check('Controller.sessKey'); * * @param string $name - * @return bool + * @return boolean * @access public */ function check($name) { @@ -153,7 +153,7 @@ class SessionHelper extends CakeSession { /** * Used to check is a session is valid in a view * - * @return bool + * @return boolean * @access public */ function valid() { @@ -165,11 +165,20 @@ class SessionHelper extends CakeSession { * Override CakeSession::write(). * This method should not be used in a view * - * @return bool + * @return boolean * @access public */ function write() { trigger_error(__('You can not write to a Session from the view', true), E_USER_WARNING); } +/** + * Session id + * + * @return string Session id + * @access public + */ + function id() { + return parent::id(); + } } -?> \ No newline at end of file +?> diff --git a/cake/tests/cases/libs/session.test.php b/cake/tests/cases/libs/session.test.php index 689a23faf..b9006b84d 100644 --- a/cake/tests/cases/libs/session.test.php +++ b/cake/tests/cases/libs/session.test.php @@ -39,6 +39,8 @@ class SessionTest extends UnitTestCase { restore_error_handler(); @$this->Session =& new CakeSession(); + $this->Session->start(); + $this->Session->_checkValid(); set_error_handler('simpleTestErrorHandler'); }