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
This commit is contained in:
phpnut 2007-11-12 01:36:20 +00:00
parent 6c0e70faa5
commit 11d295eb0c
6 changed files with 218 additions and 67 deletions

View file

@ -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;
}
}
?>
?>

View file

@ -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;
}
}
?>

View file

@ -1,6 +1,5 @@
<?php
/* SVN FILE: $Id$ */
/**
* Short description for file.
*
@ -45,6 +44,20 @@ class SessionComponent extends CakeSession {
* @access private
*/
var $__active = true;
/**
* Used to determine if Session has been started
*
* @var boolean
* @access private
*/
var $__started = false;
/**
* Used to determine if request are from an Ajax request
*
* @var boolean
* @access private
*/
var $__bare = 0;
/**
* Class constructor
*
@ -58,7 +71,27 @@ class SessionComponent extends CakeSession {
}
}
/**
* Turn sessions on if 'Session.start' is set to false in core.php
* Initializes the component, gets a reference to Controller::$param['bare'].
*
* @param object $controller A reference to the controller
* @access public
*/
function initialize(&$controller) {
$this->__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;
}
}
?>

View file

@ -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;
}

View file

@ -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();
}
}
?>
?>

View file

@ -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');
}