diff --git a/common/framework/session.php b/common/framework/session.php index a706c1074..584a87187 100644 --- a/common/framework/session.php +++ b/common/framework/session.php @@ -68,6 +68,7 @@ class Session // Do not start the session if it is already started. if (self::$_started) { + trigger_error('Session has already started', \E_USER_WARNING); return false; } @@ -96,6 +97,7 @@ class Session // Start the PHP native session. if (!session_start()) { + trigger_error('Session cannot be started', \E_USER_WARNING); return false; } @@ -124,6 +126,7 @@ class Session elseif (!$relax_key_checks) { // Hacked session! Destroy everything. + trigger_error('Session is invalid (missing key 1)', \E_USER_WARNING); $_SESSION = array(); $must_create = true; self::destroyAutologinKeys(); @@ -152,6 +155,7 @@ class Session elseif (!$relax_key_checks) { // Hacked session! Destroy everything. + trigger_error('Session is invalid (missing key 2)', \E_USER_WARNING); $_SESSION = array(); $must_create = true; self::destroyAutologinKeys(); @@ -171,6 +175,7 @@ class Session // If a member is logged in, check if the current session is valid for the member_srl. if (isset($_SESSION['RHYMIX']['login']) && $_SESSION['RHYMIX']['login'] && !self::isValid($_SESSION['RHYMIX']['login'])) { + trigger_error('Session failed validation checks for member_srl=' . intval($_SESSION['RHYMIX']['login']), \E_USER_WARNING); $_SESSION['RHYMIX']['login'] = $_SESSION['member_srl'] = false; $must_create = true; } @@ -210,6 +215,10 @@ class Session { return true; } + if (!Config::get('session.delay')) + { + return false; + } // Start the session if it contains data. if ($force || (count($_SESSION) && !headers_sent())) @@ -338,6 +347,7 @@ class Session $_SESSION['RHYMIX'] = array(); $_SESSION['RHYMIX']['login'] = false; $_SESSION['RHYMIX']['last_login'] = false; + $_SESSION['RHYMIX']['autologin_key'] = false; $_SESSION['RHYMIX']['ipaddress'] = $_SESSION['ipaddress'] = \RX_CLIENT_IP; $_SESSION['RHYMIX']['useragent'] = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; $_SESSION['RHYMIX']['language'] = \Context::getLangType(); @@ -365,6 +375,7 @@ class Session if ($member_srl && self::isValid($member_srl)) { self::login($member_srl, false); + $_SESSION['RHYMIX']['autologin_key'] = substr(self::$_autologin_key, 0, 24); } else { @@ -614,7 +625,7 @@ class Session * @param int $member_srl (optional) * @return bool */ - public static function isValid($member_srl = null) + public static function isValid($member_srl = 0) { // If no member_srl is given, the session is always valid. $member_srl = intval($member_srl) ?: (isset($_SESSION['RHYMIX']['login']) ? $_SESSION['RHYMIX']['login'] : 0); @@ -623,17 +634,11 @@ class Session return false; } - // Get the invalidation timestamp. - $invalid_before = Cache::get(sprintf('session:invalid_before:%d', $member_srl)); - if (!$invalid_before) - { - $filename = \RX_BASEDIR . sprintf('files/member_extra_info/invalid_before/%s%d.txt', getNumberingPath($member_srl), $member_srl); - $invalid_before = intval(Storage::read($filename, $invalid_before)); - } - // Check the invalidation timestamp against the current session. - if ($invalid_before && self::isStarted() && $_SESSION['RHYMIX']['last_login'] && $_SESSION['RHYMIX']['last_login'] < $invalid_before) + $validity_info = self::getValidityInfo($member_srl); + if ($validity_info->invalid_before && self::isStarted() && $_SESSION['RHYMIX']['last_login'] && $_SESSION['RHYMIX']['last_login'] < $validity_info->invalid_before) { + trigger_error('Session is invalid for member_srl=' . intval($_SESSION['RHYMIX']['login']) . ' (expired timestamp)', \E_USER_WARNING); return false; } @@ -641,10 +646,12 @@ class Session $member_info = getModel('member')->getMemberInfoByMemberSrl($member_srl); if ($member_info->denied === 'Y') { + trigger_error('Session is invalid for member_srl=' . intval($_SESSION['RHYMIX']['login']) . ' (denied)', \E_USER_WARNING); return false; } if ($member_info->limit_date && substr($member_info->limit_date, 0, 8) >= date('Ymd')) { + trigger_error('Session is invalid for member_srl=' . intval($_SESSION['RHYMIX']['login']) . ' (limited)', \E_USER_WARNING); return false; } @@ -833,6 +840,57 @@ class Session } } + /** + * Get validity information. + * + * @param int $member_srl + * @return object + */ + public static function getValidityInfo($member_srl) + { + $member_srl = intval($member_srl); + $validity_info = Cache::get(sprintf('session:validity_info:%d', $member_srl), $invalid_before); + if ($validity_info) + { + return $validity_info; + } + + $filename = \RX_BASEDIR . sprintf('files/member_extra_info/session_validity/%s%d.php', getNumberingPath($member_srl), $member_srl); + $validity_info = Storage::readPHPData($filename); + if (!$validity_info) + { + $validity_info = (object)array( + 'invalid_before' => 0, + 'invalid_autologin_keys' => array(), + 'invalid_session_keys' => array(), + ); + } + + Cache::set(sprintf('session:validity_info:%d', $member_srl), $validity_info); + return $validity_info; + } + + /** + * Set validity information. + * + * @param int $member_srl + * @param object $validity_info + * @return bool + */ + public static function setValidityInfo($member_srl, $validity_info) + { + $member_srl = intval($member_srl); + if (!$member_srl) + { + return false; + } + + $filename = \RX_BASEDIR . sprintf('files/member_extra_info/session_validity/%s%d.php', getNumberingPath($member_srl), $member_srl); + $result = Storage::writePHPData($filename, $validity_info); + Cache::set(sprintf('session:validity_info:%d', $member_srl), $validity_info); + return $result; + } + /** * Encrypt data so that it can only be decrypted in the same session. * @@ -1013,11 +1071,10 @@ class Session // Invalidate all sessions that were logged in before the current timestamp. if (self::isStarted()) { - $invalid_before = time(); - $filename = \RX_BASEDIR . sprintf('files/member_extra_info/invalid_before/%s%d.txt', getNumberingPath($member_srl), $member_srl); - Storage::write($filename, $invalid_before); - Cache::set(sprintf('session:invalid_before:%d', $member_srl), $invalid_before); - $_SESSION['RHYMIX']['last_login'] = $invalid_before; + $validity_info = self::getValidityInfo($member_srl); + $validity_info->invalid_before = time(); + self::setValidityInfo($member_srl, $validity_info); + $_SESSION['RHYMIX']['last_login'] = $validity_info->invalid_before; } else { diff --git a/tests/unit/classes/ValidatorTest.php b/tests/unit/classes/ValidatorTest.php index de4202704..0524985e2 100644 --- a/tests/unit/classes/ValidatorTest.php +++ b/tests/unit/classes/ValidatorTest.php @@ -6,8 +6,8 @@ class ValidatorTest extends \Codeception\TestCase\Test { $ob_level = ob_get_level(); - $oContext = Context::getInstance(); - $oContext->init(); + //$oContext = Context::getInstance(); + //$oContext->init(); while (ob_get_level() > $ob_level) { diff --git a/tests/unit/framework/SessionTest.php b/tests/unit/framework/SessionTest.php index 391af3244..0348ecb7a 100644 --- a/tests/unit/framework/SessionTest.php +++ b/tests/unit/framework/SessionTest.php @@ -44,7 +44,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testStart() { // Test normal start. - $this->assertTrue(Rhymix\Framework\Session::start()); + $this->assertTrue(@Rhymix\Framework\Session::start()); $this->assertTrue(isset($_COOKIE['rx_sesskey1'])); $this->assertTrue(isset($_COOKIE['rx_sesskey2'])); $this->assertNotEmpty($_SESSION['RHYMIX']['secret']); @@ -55,35 +55,35 @@ class SessionTest extends \Codeception\TestCase\Test Rhymix\Framework\Session::close(); // Test normal restart. - $this->assertTrue(Rhymix\Framework\Session::start()); + $this->assertTrue(@Rhymix\Framework\Session::start()); $this->assertEquals($session_secret, $_SESSION['RHYMIX']['secret']); $session_secret = $_SESSION['RHYMIX']['secret']; Rhymix\Framework\Session::close(); // Test missing HTTP key. unset($_COOKIE['rx_sesskey1']); - $this->assertTrue(Rhymix\Framework\Session::start()); + $this->assertTrue(@Rhymix\Framework\Session::start()); $this->assertNotEquals($session_secret, $_SESSION['RHYMIX']['secret']); $session_secret = $_SESSION['RHYMIX']['secret']; Rhymix\Framework\Session::close(); // Test missing HTTPS key. unset($_COOKIE['rx_sesskey2']); - $this->assertTrue(Rhymix\Framework\Session::start()); + $this->assertTrue(@Rhymix\Framework\Session::start()); $this->assertNotEquals($session_secret, $_SESSION['RHYMIX']['secret']); $session_secret = $_SESSION['RHYMIX']['secret']; Rhymix\Framework\Session::close(); // Test invalid HTTP key. $_COOKIE['rx_sesskey1'] = substr(md5(mt_rand()), 0, 24); - $this->assertTrue(Rhymix\Framework\Session::start()); + $this->assertTrue(@Rhymix\Framework\Session::start()); $this->assertNotEquals($session_secret, $_SESSION['RHYMIX']['secret']); $session_secret = $_SESSION['RHYMIX']['secret']; Rhymix\Framework\Session::close(); // Test invalid HTTPS key. $_COOKIE['rx_sesskey2'] = substr(md5(mt_rand()), 0, 24); - $this->assertTrue(Rhymix\Framework\Session::start()); + $this->assertTrue(@Rhymix\Framework\Session::start()); $this->assertNotEquals($session_secret, $_SESSION['RHYMIX']['secret']); $session_secret = $_SESSION['RHYMIX']['secret']; Rhymix\Framework\Session::close(); @@ -93,7 +93,7 @@ class SessionTest extends \Codeception\TestCase\Test unset($_SESSION['RHYMIX']['keys']['www.rhymix.org']['key2']); unset($_COOKIE['rx_sesskey2']); session_write_close(); - $this->assertTrue(Rhymix\Framework\Session::start()); + $this->assertTrue(@Rhymix\Framework\Session::start()); $this->assertEquals($session_secret, $_SESSION['RHYMIX']['secret']); $session_secret = $_SESSION['RHYMIX']['secret']; Rhymix\Framework\Session::close(); @@ -105,13 +105,14 @@ class SessionTest extends \Codeception\TestCase\Test $_SESSION = array(); unset($_COOKIE['PHPSESSID']); - $this->assertFalse(Rhymix\Framework\Session::start()); + $this->assertFalse(@Rhymix\Framework\Session::start()); $this->assertFalse(Rhymix\Framework\Session::isStarted()); $this->assertFalse(Rhymix\Framework\Session::checkStart()); $this->assertFalse(Rhymix\Framework\Session::isStarted()); + Rhymix\Framework\Session::close(); $_SESSION['foo'] = 'bar'; - $this->assertTrue(Rhymix\Framework\Session::checkStart()); + $this->assertTrue(@Rhymix\Framework\Session::checkStart()); $this->assertTrue(Rhymix\Framework\Session::isStarted()); $this->assertEquals('bar', $_SESSION['foo']); $this->assertEquals('bar', Rhymix\Framework\Session::get('foo')); @@ -119,13 +120,13 @@ class SessionTest extends \Codeception\TestCase\Test $_SESSION = array(); unset($_COOKIE['PHPSESSID']); - $this->assertTrue(Rhymix\Framework\Session::checkStart(true)); + $this->assertTrue(@Rhymix\Framework\Session::checkStart(true)); $this->assertTrue(Rhymix\Framework\Session::isStarted()); Rhymix\Framework\Session::close(); $_SESSION = array(); unset($_COOKIE['PHPSESSID']); - $this->assertTrue(Rhymix\Framework\Session::start(true)); + $this->assertTrue(@Rhymix\Framework\Session::start(true)); $this->assertTrue(Rhymix\Framework\Session::isStarted()); } @@ -136,7 +137,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testRefresh() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $session_secret = $_SESSION['RHYMIX']['secret']; $key1 = $_SESSION['RHYMIX']['keys']['www.rhymix.org']['key1']; $key2 = $_SESSION['RHYMIX']['keys']['www.rhymix.org']['key2']; @@ -144,7 +145,7 @@ class SessionTest extends \Codeception\TestCase\Test $_SESSION['RHYMIX']['keys']['www.rhymix.org']['key2_time'] = time() - 3600; Rhymix\Framework\Session::close(); - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertNotEquals($key1, $_SESSION['RHYMIX']['keys']['www.rhymix.org']['key1']); $this->assertNotEquals($key2, $_SESSION['RHYMIX']['keys']['www.rhymix.org']['key2']); $this->assertEquals($key1, $_SESSION['RHYMIX']['keys']['www.rhymix.org']['key1_prev']); @@ -155,7 +156,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testClose() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertEquals(\PHP_SESSION_ACTIVE, session_status()); Rhymix\Framework\Session::close(); $this->assertEquals(\PHP_SESSION_NONE, session_status()); @@ -163,7 +164,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testDestroy() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertTrue(isset($_SESSION['RHYMIX'])); Rhymix\Framework\Session::destroy(); $this->assertFalse(isset($_SESSION['RHYMIX'])); @@ -171,7 +172,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testLoginLogout() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertFalse($_SESSION['RHYMIX']['login']); $this->assertFalse($_SESSION['member_srl']); $this->assertFalse($_SESSION['is_logged']); @@ -192,7 +193,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testIsStarted() { $this->assertFalse(Rhymix\Framework\Session::isStarted()); - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertTrue(Rhymix\Framework\Session::isStarted()); Rhymix\Framework\Session::close(); $this->assertFalse(Rhymix\Framework\Session::isStarted()); @@ -200,7 +201,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testIsMember() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertFalse(Rhymix\Framework\Session::isMember()); Rhymix\Framework\Session::login(42); @@ -211,7 +212,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testIsAdmin() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertFalse(Rhymix\Framework\Session::isAdmin()); Rhymix\Framework\Session::login(42); @@ -228,7 +229,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testIsTrusted() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $_SESSION['RHYMIX']['keys']['www.rhymix.org']['trusted'] = 0; $this->assertFalse(Rhymix\Framework\Session::isTrusted()); @@ -240,27 +241,24 @@ class SessionTest extends \Codeception\TestCase\Test public function testIsValid() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $member_srl = 4; - Rhymix\Framework\Session::login($member_srl); + $this->assertTrue(Rhymix\Framework\Session::login($member_srl)); + $validity_info = Rhymix\Framework\Session::getValidityInfo($member_srl); + $this->assertTrue(is_object($validity_info)); + $this->assertTrue(isset($validity_info->invalid_before)); - $invalid_before = time() - 300; - $filename = \RX_BASEDIR . sprintf('files/member_extra_info/invalid_before/%s%d.txt', getNumberingPath($member_srl), $member_srl); - Rhymix\Framework\Storage::write($filename, $invalid_before); - Rhymix\Framework\Cache::set(sprintf('session:invalid_before:%d', $member_srl), $invalid_before); + $validity_info->invalid_before = time() - 300; + $this->assertTrue(Rhymix\Framework\Session::setValidityInfo($member_srl, $validity_info)); $this->assertTrue(Rhymix\Framework\Session::isValid()); - $invalid_before = time() + 300; - $filename = \RX_BASEDIR . sprintf('files/member_extra_info/invalid_before/%s%d.txt', getNumberingPath($member_srl), $member_srl); - Rhymix\Framework\Storage::write($filename, $invalid_before); - Rhymix\Framework\Cache::set(sprintf('session:invalid_before:%d', $member_srl), $invalid_before); - $this->assertFalse(Rhymix\Framework\Session::isValid()); + $validity_info->invalid_before = time() + 300; + $this->assertTrue(Rhymix\Framework\Session::setValidityInfo($member_srl, $validity_info)); + $this->assertFalse(@Rhymix\Framework\Session::isValid()); - $invalid_before = time(); - $filename = \RX_BASEDIR . sprintf('files/member_extra_info/invalid_before/%s%d.txt', getNumberingPath($member_srl), $member_srl); - Rhymix\Framework\Storage::write($filename, $invalid_before); - Rhymix\Framework\Cache::set(sprintf('session:invalid_before:%d', $member_srl), $invalid_before); + $validity_info->invalid_before = time(); + $this->assertTrue(Rhymix\Framework\Session::setValidityInfo($member_srl, $validity_info)); $this->assertTrue(Rhymix\Framework\Session::isValid()); Rhymix\Framework\Session::close(); @@ -268,7 +266,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testGetMemberSrl() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertEquals(false, Rhymix\Framework\Session::getMemberSrl()); Rhymix\Framework\Session::login(42); @@ -279,7 +277,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testGetMemberInfo() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertEquals(false, Rhymix\Framework\Session::getMemberInfo()); Rhymix\Framework\Session::login(42); @@ -296,7 +294,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testGetSetLanguage() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertEquals(\Context::getLangType(), Rhymix\Framework\Session::getLanguage()); Rhymix\Framework\Session::setLanguage('ja'); @@ -307,7 +305,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testGetSetTimezone() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertEquals(config('locale.default_timezone'), Rhymix\Framework\Session::getTimezone()); Rhymix\Framework\Session::setTimezone('Asia/Beijing'); @@ -318,7 +316,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testTokens() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $token1 = Rhymix\Framework\Session::createToken(); $this->assertTrue(ctype_alnum($token1)); @@ -339,7 +337,7 @@ class SessionTest extends \Codeception\TestCase\Test public function testEncryption() { - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $plaintext = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; $ciphertext = Rhymix\Framework\Session::encrypt($plaintext); @@ -349,7 +347,7 @@ class SessionTest extends \Codeception\TestCase\Test Rhymix\Framework\Session::destroy(); $this->assertFalse(Rhymix\Framework\Session::decrypt($ciphertext)); - Rhymix\Framework\Session::start(); + @Rhymix\Framework\Session::start(); $this->assertFalse(Rhymix\Framework\Session::decrypt($ciphertext)); Rhymix\Framework\Session::close();