Index: branches/5.1.x/core/kernel/session/session.php
===================================================================
diff -u -r12453 -r12657
--- branches/5.1.x/core/kernel/session/session.php (.../session.php) (revision 12453)
+++ branches/5.1.x/core/kernel/session/session.php (.../session.php) (revision 12657)
@@ -1,6 +1,6 @@
SetCookieName('my_sid_cookie');
$session->SetGETName('sid');
$session->InitSession();
-
...
//link output:
@@ -69,7 +72,11 @@
*/
-//Implements session storage in the database
+
+/**
+ * Implements Session Store in the Database
+ *
+ */
class SessionStorage extends kDBBase {
var $Expiration;
@@ -103,6 +110,29 @@
$this->SessionTimeout = $new_timeout;
}
+ /**
+ * Calculates browser signature
+ *
+ * @return string
+ */
+ function _getBrowserSignature()
+ {
+ $signature_parts = Array(
+ 'HTTP_USER_AGENT', 'SERVER_PROTOCOL',
+ 'HTTP_ACCEPT_CHARSET', 'HTTP_ACCEPT_ENCODING', 'HTTP_ACCEPT_LANGUAGE'
+ );
+
+ $ret = '';
+
+ foreach ($signature_parts as $signature_part) {
+ if (array_key_exists($signature_part, $_SERVER)) {
+ $ret .= '&|&' . $_SERVER[$signature_part];
+ }
+ }
+
+ return md5( substr($ret, 3) );
+ }
+
function StoreSession(&$session, $additional_fields = Array())
{
if (defined('IS_INSTALL') && IS_INSTALL && !$this->Application->TableFound($this->TableName)) {
@@ -111,9 +141,19 @@
$fields_hash = Array (
$this->IDField => $session->SID,
- $this->TimestampField => $session->Expiration
+ $this->TimestampField => $session->Expiration,
);
+ if (!defined('IS_INSTALL') || !IS_INSTALL) {
+ // this column was added only in 5.0.1 version,
+ // so accessing it while database is not upgraded
+ // will result in admin's inability to login inside
+ // installator
+ $fields_hash['BrowserSignature'] = $this->_getBrowserSignature();
+ }
+
+ // default values + additional values + values set during this script run
+ $additional_fields = array_merge($additional_fields, $this->DirectVars); // used 2 times later
$fields_hash = array_merge($fields_hash, $additional_fields);
$this->Conn->doInsert($fields_hash, $this->TableName);
@@ -143,16 +183,29 @@
function LocateSession($sid)
{
- $query = ' SELECT * FROM '.$this->TableName.' WHERE '.$this->IDField.' = '.$this->Conn->qstr($sid);
- $result = $this->Conn->GetRow($query);
+ $sql = 'SELECT *
+ FROM ' . $this->TableName . '
+ WHERE ' . $this->IDField . ' = ' . $this->Conn->qstr($sid);
+ $result = $this->Conn->GetRow($sql);
if ($result === false) {
return false;
}
- $this->DirectVars = $result;
+ // perform security checks to ensure, that session is used by it's creator
+ if ($this->Application->ConfigValue('SessionBrowserSignatureCheck') && ($result['BrowserSignature'] != $this->_getBrowserSignature())) {
+ return false;
+ }
+ if ($this->Application->ConfigValue('SessionIPAddressCheck') && ($result['IpAddress'] != $_SERVER['REMOTE_ADDR'])) {
+ // most secure, except for cases where NAT (Network Address Translation)
+ // is used and two or more computers can have same IP address
+ return false;
+ }
+
+ $this->DirectVars = $result;
$this->Expiration = $result[$this->TimestampField];
+
return true;
}
@@ -238,9 +291,9 @@
unset($this->OriginalData[$var]);
}
- function GetFromData(&$session, $var)
+ function GetFromData(&$session, $var, $default = false)
{
- return getArrayValue($this->OriginalData, $var);
+ return array_key_exists($var, $this->OriginalData) ? $this->OriginalData[$var] : $default;
}
function GetExpiredSIDs()
@@ -275,7 +328,7 @@
// delete debugger ouputs left of expired sessions
foreach ($expired_sids as $expired_sid) {
- $debug_file = (defined('WRITEABLE') ? WRITEABLE : FULL_PATH.'/kernel').'/cache/debug_@'.$expired_sid.'@.txt';
+ $debug_file = WRITEABLE . '/cache/debug_@' . $expired_sid . '@.txt';
if (file_exists($debug_file)) {
@unlink($debug_file);
}
@@ -397,6 +450,7 @@
define('smGET_ONLY', 3);
define('smCOOKIES_AND_GET', 4);
+
class Session extends kBase {
var $Checkers;
@@ -420,6 +474,13 @@
var $SessionSet = false;
/**
+ * Session ID is used from GET
+ *
+ * @var bool
+ */
+ var $_fromGet = false;
+
+ /**
* Enter description here...
*
* @var SessionStorage
@@ -436,16 +497,17 @@
var $Data;
/**
- * Names of optional session keys (which does not need to be always stored
+ * Names of optional session keys with their optional values (which does not need to be always stored)
*
- * @var array
+ * @var Array
*/
- var $OptionalData = array();
+ var $OptionalData = Array ();
- function Session($mode=smAUTO)
+ function Session($mode = smAUTO)
{
parent::kBase();
+
$this->SetMode($mode);
}
@@ -536,7 +598,7 @@
$expired_sids = $this->DeleteExpired();
$my_sid_expired = in_array($this->CachedSID, $expired_sids);
- if ( ($expired_sids && $my_sid_expired) || ($this->CachedSID && !$this->SessionSet) ) {
+ if ( ($expired_sids && $my_sid_expired) || ($this->CachedSID && !$this->_fromGet && !$this->SessionSet) ) {
$this->RemoveSessionCookie();
// true was here to force new session creation, but I (kostja) used
// RemoveCookie a line above, to avoid redirect loop with expired sid
@@ -551,81 +613,58 @@
}
}
+ /**
+ * This is redirect from https to http or via versa
+ *
+ * @return bool
+ */
function IsHTTPSRedirect()
{
- $http_referer = getArrayValue($_SERVER, 'HTTP_REFERER');
+ $http_referer = array_key_exists('HTTP_REFERER', $_SERVER) ? $_SERVER['HTTP_REFERER'] : false;
+
return (
( PROTOCOL == 'https://' && preg_match('#http:\/\/#', $http_referer) )
||
( PROTOCOL == 'http://' && preg_match('#https:\/\/#', $http_referer) )
);
}
- function CheckReferer($for_cookies=0)
+ /**
+ * Helper method for detecting cookie availability
+ *
+ * @return bool
+ */
+ function _checkCookieReferer()
{
- if (!$for_cookies) {
- if ( !$this->Application->ConfigValue('SessionReferrerCheck') || $_SERVER['REQUEST_METHOD'] != 'POST') {
- return true;
- }
- }
- $path = preg_replace('/admin[\/]{0,1}$/', '', $this->CookiePath); // removing /admin for compatability with in-portal (in-link/admin/add_link.php)
+ // removing /admin for compatability with in-portal (in-link/admin/add_link.php)
+ $path = preg_replace('/admin[\/]{0,1}$/', '', $this->CookiePath);
$reg = '#^'.preg_quote(PROTOCOL.ltrim($this->CookieDomain, '.').$path).'#';
- return preg_match($reg, getArrayValue($_SERVER, 'HTTP_REFERER') ) || (defined('IS_POPUP') && IS_POPUP);
- }
- /*function CheckDuplicateCookies()
- {
- if (isset($_SERVER['HTTP_COOKIE'])) {
- $cookie_str = $_SERVER['HTTP_COOKIE'];
- $cookies = explode('; ', $cookie_str);
- $all_cookies = array();
- foreach ($cookies as $cookie) {
- list($name, $value) = explode('=', $cookie);
- if (isset($all_cookies[$name])) {
- //double cookie name!!!
- $this->RemoveCookie($name);
- }
- else $all_cookies[$name] = $value;
- }
- }
+ return preg_match($reg, getArrayValue($_SERVER, 'HTTP_REFERER') );
}
- function RemoveCookie($name)
- {
- $path = $_SERVER['PHP_SELF'];
- $path_parts = explode('/', $path);
- $cur_path = '';
- setcookie($name, false, null, $cur_path);
- foreach ($path_parts as $part) {
- $cur_path .= $part;
- setcookie($name, false, null, $cur_path);
- $cur_path .= '/';
- setcookie($name, false, null, $cur_path);
- }
- }*/
-
function CheckIfCookiesAreOn()
{
-// $this->CheckDuplicateCookies();
- if ($this->Mode == smGET_ONLY)
- {
+ if ($this->Mode == smGET_ONLY) {
//we don't need to bother checking if we would not use it
$this->CookiesEnabled = false;
return;
}
+
$http_query =& $this->Application->recallObject('HTTPQuery');
- $cookies_on = isset($http_query->Cookie['cookies_on']); // not good here
+ $cookies_on = array_key_exists('cookies_on', $http_query->Cookie); // not good here
$get_sid = getArrayValue($http_query->Get, $this->GETName);
- if ($this->IsHTTPSRedirect() && $get_sid) { //Redirect from http to https on different domain
+
+ if ($this->IsHTTPSRedirect() && $get_sid) { // Redirect from http to https on different domain
$this->OriginalMode = $this->Mode;
$this->SetMode(smGET_ONLY);
}
if (!$cookies_on || $this->IsHTTPSRedirect()) {
//If referer is our server, but we don't have our cookies_on, it's definetly off
$is_install = defined('IS_INSTALL') && IS_INSTALL;
- if (!$is_install && $this->CheckReferer(1) && !$this->Application->GetVar('admin') && !$this->IsHTTPSRedirect()) {
+ if (!$is_install && $this->_checkCookieReferer() && !$this->Application->GetVar('admin') && !$this->IsHTTPSRedirect()) {
$this->CookiesEnabled = false;
}
else {
@@ -634,8 +673,10 @@
$this->SetCookie('cookies_on', 1, adodb_mktime() + 31104000); //one year should be enough
}
}
- else
+ else {
$this->CookiesEnabled = true;
+ }
+
return $this->CookiesEnabled;
}
@@ -653,53 +694,54 @@
function Check()
{
- // we should check referer if cookies are disabled, and in combined mode
- // auto mode would detect cookies, get only mode would turn it off - so we would get here
- // and we don't care about referal in cookies only mode
+ // don't check referer here, because it doesn't provide any security option and can be easily falsified
- if ( $this->Mode != smCOOKIES_ONLY && (!$this->CookiesEnabled || $this->Mode == smCOOKIES_AND_GET) ) {
- if (!$this->CheckReferer())
- return false;
- }
-
$sid = $this->GetPassedSIDValue();
- if (empty($sid)) return false;
+ if (empty($sid)) {
+ return false;
+ }
//try to load session by sid, if everything is fine
$result = $this->LoadSession($sid);
- $this->SessionSet = $result;
+ $this->SessionSet = $result; // fake front-end session will given "false" here
return $result;
}
function LoadSession($sid)
{
if( $this->Storage->LocateSession($sid) ) {
- //if we have session with such SID - get its expiration
+ // if we have session with such SID - get its expiration
$this->Expiration = $this->Storage->GetExpiration();
- //If session has expired
+ // If session has expired
if ($this->Expiration < adodb_mktime()) {
$this->Destroy();
// when Destory methods calls SetSession inside and new session get created
return $this->SessionSet;
}
- //Otherwise it's ok
+ // Otherwise it's ok
return true;
}
- else //fake or deleted due to expiration SID
+ else {
+ // fake or deleted due to expiration SID
return false;
+ }
}
function GetPassedSIDValue($use_cache = 1)
{
- if (!empty($this->CachedSID) && $use_cache) return $this->CachedSID;
+ if (!empty($this->CachedSID) && $use_cache) {
+ return $this->CachedSID;
+ }
+
$http_query =& $this->Application->recallObject('HTTPQuery');
$get_sid = getArrayValue($http_query->Get, $this->GETName);
+ $sid_from_get = $get_sid ? true : false;
if ($this->Application->GetVar('admin') == 1 && $get_sid) {
$sid = $get_sid;
@@ -709,30 +751,37 @@
case smAUTO:
//Cookies has the priority - we ignore everything else
$sid = $this->CookiesEnabled ? $this->GetSessionCookie() : $get_sid;
+
+ if ($this->CookiesEnabled) {
+ $sid_from_get = false;
+ }
break;
+
case smCOOKIES_ONLY:
$sid = $this->GetSessionCookie();
break;
+
case smGET_ONLY:
$sid = $get_sid;
break;
+
case smCOOKIES_AND_GET:
$cookie_sid = $this->GetSessionCookie();
//both sids should match if cookies are enabled
- if (!$this->CookiesEnabled || ($cookie_sid == $get_sid))
- {
+ if (!$this->CookiesEnabled || ($cookie_sid == $get_sid)) {
$sid = $get_sid; //we use get here just in case cookies are disabled
}
- else
- {
+ else {
$sid = '';
+ $sid_from_get = false;
}
break;
}
}
-
$this->CachedSID = $sid;
+ $this->_fromGet = $sid_from_get;
+
return $this->CachedSID;
}
@@ -755,18 +804,21 @@
*/
function GenerateSID()
{
- list($usec, $sec) = explode(" ",microtime());
+ list ($usec, $sec) = explode(' ', microtime());
$sid_part_1 = substr($usec, 4, 4);
- $sid_part_2 = mt_rand(1,9);
+ $sid_part_2 = mt_rand(1, 9);
$sid_part_3 = substr($sec, 6, 4);
$digit_one = substr($sid_part_1, 0, 1);
+
if ($digit_one == 0) {
- $digit_one = mt_rand(1,9);
+ $digit_one = mt_rand(1, 9);
$sid_part_1 = preg_replace('/^0/', '', $sid_part_1);
- $sid_part_1=$digit_one.$sid_part_1;
+ $sid_part_1 = $digit_one . $sid_part_1;
}
- $this->setSID($sid_part_1.$sid_part_2.$sid_part_3);
+
+ $this->setSID($sid_part_1 . $sid_part_2 . $sid_part_3);
+
return $this->SID;
}
@@ -778,43 +830,61 @@
*/
function setSID($new_sid)
{
- $this->SID = $this->CachedSID = $new_sid;
+ $this->SID /*= $this->CachedSID*/ = $new_sid; // don't set cached sid here
$this->Application->SetVar($this->GETName,$new_sid);
}
function NeedSession()
{
$data = $this->Data->GetParams();
+
$data_keys = array_keys($data);
- $optional_keys = array_unique($this->OptionalData);
+ $optional_keys = array_keys($this->OptionalData);
$real_keys = array_diff($data_keys, $optional_keys);
+
return $real_keys ? true : false;
}
function SetSession($force = false)
{
- if ($this->SessionSet && !$force) return true;
+ if ($this->SessionSet && !$force) {
+ return true;
+ }
+
if (!$force && !($this->Application->IsAdmin() || $this->Application->GetVar('admin')) && !$this->NeedSession()) {
// don't create session (in db) on Front-End, when sid is present (GPC), but data in db isn't
- $this->GenerateSID();
+ if ($this->_fromGet) {
+ // set sid, that was given in GET
+ $this->setSID( $this->GetPassedSIDValue() );
+ } else {
+ // re-generate sid only, when cookies are used
+ $this->GenerateSID();
+ }
return false;
}
- if (!$this->SID || $force) $this->GenerateSID();
+ if (!$this->SID || $force) {
+ $this->GenerateSID();
+ }
+
$this->Expiration = adodb_mktime() + $this->SessionTimeout;
+
switch ($this->Mode) {
case smAUTO:
if ($this->CookiesEnabled) {
$this->SetSessionCookie();
}
break;
+
case smGET_ONLY:
break;
+
case smCOOKIES_ONLY:
case smCOOKIES_AND_GET:
$this->SetSessionCookie();
break;
}
+
$this->Storage->StoreSession($this);
if ($this->Application->IsAdmin() || $this->Special == 'admin') {
@@ -901,29 +971,38 @@
$this->SID = $this->CachedSID = '';
$this->SessionSet = false;
- if ($this->CookiesEnabled) $this->SetSessionCookie(); //will remove the cookie due to value (sid) is empty
+ if ($this->CookiesEnabled) {
+ $this->SetSessionCookie(); //will remove the cookie due to value (sid) is empty
+ }
$this->SetSession(true); //will create a new session, true to force
}
function NeedQueryString($use_cache = 1)
{
- if ($this->CachedNeedQueryString != null && $use_cache) return $this->CachedNeedQueryString;
+ if ($this->CachedNeedQueryString != null && $use_cache) {
+ return $this->CachedNeedQueryString;
+ }
$result = false;
- switch ($this->Mode)
- {
+ switch ($this->Mode) {
case smAUTO:
- if (!$this->CookiesEnabled) $result = true;
+ if (!$this->CookiesEnabled) {
+ $result = true;
+ }
break;
+
/*case smCOOKIES_ONLY:
break;*/
+
case smGET_ONLY:
case smCOOKIES_AND_GET:
$result = true;
break;
}
+
$this->CachedNeedQueryString = $result;
+
return $result;
}
@@ -932,9 +1011,9 @@
$this->Data->AddParams($this->Storage->LoadData($this));
}
- function PrintSession($comment='')
+ function PrintSession($comment = '')
{
- if($this->Application->isDebugMode() && constOn('DBG_SHOW_SESSIONDATA')) {
+ if (defined('DEBUG_MODE') && $this->Application->isDebugMode() && constOn('DBG_SHOW_SESSIONDATA')) {
// dump session data
$this->Application->Debugger->appendHTML('SessionStorage [' . ($this->RecallVar('admin') == 1 ? 'Admin' : 'Front-End') . '] ('.$comment.'):');
$session_data = $this->Data->GetParams();
@@ -945,8 +1024,25 @@
}
}
$this->Application->Debugger->dumpVars($session_data);
+
+ if (!$this->RecallVar('admin')) {
+ // dump real keys (only for front-end)
+ $data_keys = array_keys($session_data);
+ $optional_keys = array_keys($this->OptionalData);
+ $real_keys = array_diff($data_keys, $optional_keys);
+
+ if ($real_keys) {
+ $ret = '';
+ foreach ($real_keys as $real_key) {
+ $ret .= '[' . $real_key . '] = [' . $session_data[$real_key] . ']
';
+ }
+
+ $this->Application->Debugger->appendHTML('Real Keys:
' . $ret);
+ }
+ }
}
- if ($this->Application->isDebugMode() && constOn('DBG_SHOW_PERSISTENTDATA')) {
+
+ if (defined('DEBUG_MODE') && $this->Application->isDebugMode() && constOn('DBG_SHOW_PERSISTENTDATA')) {
// dump persistent session data
if ($this->Storage->PersistentVars) {
$this->Application->Debugger->appendHTML('Persistant Session:');
@@ -1019,6 +1115,10 @@
elseif ($this->Application->GetVar('admin')) {
// admin checking by session data to prevent recursive session save
if (!$this->RecallVar('admin')) {
+ // bug: we get recursion in this place, when cookies are disabled in browser and we are browsing
+ // front-end in admin's frame (front-end session is initialized using admin's sid and they are
+ // mixed together)
+
$admin_session =& $this->Application->recallObject('Session.admin');
/* @var $admin_session Session */
@@ -1055,6 +1155,11 @@
$params['__URLENCODE__'] = 1; // uses "&" instead of "&" for url part concatenation + replaces "\" to "%5C" (works in HTML)
+
+ if ($this->Application->GetVar('admin') && !array_key_exists('admin', $params) && !defined('EDITING_MODE')) {
+ $params['editing_mode'] = ''; // used in kApplication::Run
+ }
+
$params = array_merge($this->Application->getPassThroughVariables($params), $params);
$ret = $this->Application->BuildEnv($t, $params, 'all');
@@ -1068,9 +1173,20 @@
function StoreVar($name, $value, $optional = false)
{
$this->Data->Set($name, $value);
+
if ($optional) {
- $this->OptionalData[] = $name;
+ // make variable optional, also remember optional value
+ $this->OptionalData[$name] = $value;
}
+ elseif (!$optional && array_key_exists($name, $this->OptionalData)) {
+ if ($this->OptionalData[$name] == $value) {
+ // same value as optional -> don't remove optional mark
+ return ;
+ }
+
+ // make variable non-optional
+ unset($this->OptionalData[$name]);
+ }
}
function StorePersistentVar($name, $value)
@@ -1122,7 +1238,14 @@
*/
function RestoreVar($name)
{
- return $this->StoreVar($name, $this->Storage->GetFromData($this, $name));
+ $value = $this->Storage->GetFromData($this, $name, '__missing__');
+
+ if ($value === '__missing__') {
+ // there is nothing to restore (maybe session was not saved), look in optional variable values
+ $value = array_key_exists($name, $this->OptionalData) ? $this->OptionalData[$name] : false;
+ }
+
+ return $this->StoreVar($name, $value);
}
function GetField($var_name, $default = false)
@@ -1162,6 +1285,4 @@
return $ret;
}
-}
-
-?>
\ No newline at end of file
+}
\ No newline at end of file