Merge pull request #707 from kijin/pr/session-fixes

세션 기능 개선 #567
This commit is contained in:
Kijin Sung 2017-02-13 00:21:41 +09:00 committed by GitHub
commit 256d078394
3 changed files with 114 additions and 59 deletions

View file

@ -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
{

View file

@ -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)
{

View file

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