diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php
index 0fd9fa872..5a5b13820 100644
--- a/classes/context/Context.class.php
+++ b/classes/context/Context.class.php
@@ -338,59 +338,23 @@ class Context
array(&$oSessionController, 'open'), array(&$oSessionController, 'close'), array(&$oSessionModel, 'read'), array(&$oSessionController, 'write'), array(&$oSessionController, 'destroy'), array(&$oSessionController, 'gc')
);
}
+
+ // start session
+ $relax_key_checks = ($this->act === 'procFileUpload' && preg_match('/shockwave\s?flash/i', $_SERVER['HTTP_USER_AGENT']));
+ Rhymix\Framework\Session::start(false, $relax_key_checks);
- // start session if it was previously started
- $session_name = session_name();
- $session_id = NULL;
- if($session_id = $_POST[$session_name])
- {
- session_id($session_id);
- }
- else
- {
- $session_id = $_COOKIE[$session_name];
- }
-
- if($session_id !== NULL || !config('session.delay'))
- {
- $this->setCacheControl(0, false);
- session_start();
- }
- else
- {
- $this->setCacheControl(-1, true);
- $_SESSION = array();
- }
-
+ // start output buffer
ob_start();
// set authentication information in Context and session
- if(self::isInstalled())
+ if (self::isInstalled())
{
$oModuleModel = getModel('module');
$oModuleModel->loadModuleExtends();
- $oMemberModel = getModel('member');
- $oMemberController = getController('member');
-
- if($oMemberController && $oMemberModel)
+ if (Rhymix\Framework\Session::getMemberSrl())
{
- // if signed in, validate it.
- if($oMemberModel->isLogged())
- {
- $oMemberController->setSessionInfo();
- }
- // check auto sign-in
- elseif($_COOKIE['xeak'])
- {
- $oMemberController->doAutologin();
- }
-
- self::set('is_logged', $oMemberModel->isLogged());
- if($oMemberModel->isLogged())
- {
- self::set('logged_info', $oMemberModel->getLoggedInfo());
- }
+ getController('member')->setSessionInfo();
}
}
@@ -428,7 +392,7 @@ class Context
*/
public static function getSessionStatus()
{
- return (session_id() !== '');
+ return Rhymix\Framework\Session::isStarted();
}
/**
@@ -436,21 +400,9 @@ class Context
*
* @return void
*/
- public static function checkSessionStatus($force_start = false)
+ public static function checkSessionStatus($force = false)
{
- if(self::getSessionStatus())
- {
- return true;
- }
- if($force_start || (count($_SESSION) && !headers_sent()))
- {
- $tempSession = $_SESSION;
- unset($_SESSION);
- session_start();
- $_SESSION = $tempSession;
- return true;
- }
- return false;
+ return Rhymix\Framework\Session::checkStart($force);
}
/**
@@ -467,9 +419,9 @@ class Context
}
// Check session status and close it if open.
- if (self::checkSessionStatus())
+ if (Rhymix\Framework\Session::checkStart())
{
- session_write_close();
+ Rhymix\Framework\Session::close();
}
}
@@ -703,97 +655,7 @@ class Context
*/
public function checkSSO()
{
- // pass if it's not GET request or XE is not yet installed
- if(!config('use_sso') || Rhymix\Framework\UA::isRobot())
- {
- return TRUE;
- }
- $checkActList = array('rss' => 1, 'atom' => 1);
- if(self::getRequestMethod() != 'GET' || !self::isInstalled() || isset($checkActList[self::get('act')]))
- {
- return TRUE;
- }
-
- // pass if default URL is not set
- $default_url = trim($this->db_info->default_url);
- if(!$default_url)
- {
- return TRUE;
- }
-
- if(substr_compare($default_url, '/', -1) !== 0)
- {
- $default_url .= '/';
- }
-
- // Get current site information (only the base URL, not the full URL)
- $current_site = self::getRequestUri();
-
- // Step 1: if the current site is not the default site, send SSO validation request to the default site
- if($default_url !== $current_site && !self::get('sso_response') && $_COOKIE['sso'] !== md5($current_site))
- {
- // Set sso cookie to prevent multiple simultaneous SSO validation requests
- setcookie('sso', md5($current_site), 0, '/');
-
- // Redirect to the default site
- $sso_request = Rhymix\Framework\Security::encrypt(Rhymix\Framework\URL::getCurrentURL());
- $redirect_url = $default_url . '?sso_request=' . urlencode($sso_request);
- header('Location:' . $redirect_url);
- return false;
- }
-
- // Step 2: receive and process SSO validation request at the default site
- if($default_url === $current_site && self::get('sso_request'))
- {
- // Get the URL of the origin site
- $sso_request = Rhymix\Framework\Security::decrypt(self::get('sso_request'));
- if (!$sso_request || !preg_match('!^https?://!', $sso_request))
- {
- self::displayErrorPage('SSO Error', 'Invalid SSO Request', 400);
- return false;
- }
-
- // Check that the origin site is a valid site in this XE installation (to prevent open redirect vuln)
- if(!getModel('module')->getSiteInfoByDomain(rtrim($url, '/'))->site_srl)
- {
- self::displayErrorPage('SSO Error', 'Invalid SSO Request', 400);
- return false;
- }
-
- // Redirect back to the origin site
- $sso_response = Rhymix\Framework\Security::encrypt(session_id());
- header('Location: ' . Rhymix\Framework\URL::modifyURL($sso_request, array('sso_response' => $sso_response)));
- return false;
- }
-
- // Step 3: back at the origin site, set session ID to be the same as the default site
- if($default_url !== $current_site && self::get('sso_response'))
- {
- // Check SSO response
- $sso_response = Rhymix\Framework\Security::decrypt(self::get('sso_response'));
- if ($sso_response === false)
- {
- self::displayErrorPage('SSO Error', 'Invalid SSO Response', 400);
- return false;
- }
-
- // Check that the response was given by the default site (to prevent session fixation CSRF)
- if(isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], $default_url) !== 0)
- {
- self::displayErrorPage('SSO Error', 'Invalid SSO Response', 400);
- return false;
- }
-
- // Set session ID
- setcookie(session_name(), $sso_response);
-
- // Finally, redirect to the originally requested URL
- header('Location: ' . Rhymix\Framework\URL::getCurrentURL(array('sso_response' => null)));
- return false;
- }
-
- // If none of the conditions above apply, proceed normally
- return TRUE;
+ return !Rhymix\Framework\Session::checkSSO();
}
/**
diff --git a/classes/db/DBCubrid.class.php b/classes/db/DBCubrid.class.php
index 1491eb73b..7963ae620 100644
--- a/classes/db/DBCubrid.class.php
+++ b/classes/db/DBCubrid.class.php
@@ -956,6 +956,29 @@ class DBCubrid extends DB
}
}
+ /**
+ * Drop table
+ *
+ * @param string $name
+ * @return bool
+ */
+ function dropTable($table_name)
+ {
+ // Generate the drop query
+ $query = sprintf('drop class "%s"', $this->addQuotes($this->prefix . $table_name));
+
+ // Execute the drop query
+ $output = $this->_query($query);
+ if($output)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
/**
* Handles insertAct
* @param Object $queryObject
diff --git a/classes/db/DBMssql.class.php b/classes/db/DBMssql.class.php
index a1473ac73..b0ffa4bd2 100644
--- a/classes/db/DBMssql.class.php
+++ b/classes/db/DBMssql.class.php
@@ -748,6 +748,29 @@ class DBMssql extends DB
}
}
+ /**
+ * Drop table
+ *
+ * @param string $name
+ * @return bool
+ */
+ function dropTable($table_name)
+ {
+ // Generate the drop query
+ $query = sprintf('DROP TABLE %s', $this->addQuotes($this->prefix . $table_name));
+
+ // Execute the drop query
+ $output = $this->_query($query);
+ if($output)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
/**
* Handles insertAct
* @todo Lookup _filterNumber against sql injection - see if it is still needed and how to integrate
diff --git a/classes/db/DBMysql.class.php b/classes/db/DBMysql.class.php
index 27e16b315..5ec68cc04 100644
--- a/classes/db/DBMysql.class.php
+++ b/classes/db/DBMysql.class.php
@@ -682,6 +682,29 @@ class DBMysql extends DB
}
}
+ /**
+ * Drop table
+ *
+ * @param string $name
+ * @return bool
+ */
+ function dropTable($table_name)
+ {
+ // Generate the drop query
+ $query = sprintf('DROP TABLE `%s`', $this->addQuotes($this->prefix . $table_name));
+
+ // Execute the drop query
+ $output = $this->_query($query);
+ if($output)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
/**
* Handles insertAct
* @param Object $queryObject
diff --git a/classes/mobile/Mobile.class.php b/classes/mobile/Mobile.class.php
index 5e3972ca8..e6bad61ce 100644
--- a/classes/mobile/Mobile.class.php
+++ b/classes/mobile/Mobile.class.php
@@ -45,12 +45,22 @@ class Mobile
// Try to detect from URL arguments and cookies, and finally fall back to user-agent detection.
$m = Context::get('m');
- $cookie = (isset($_COOKIE['mobile']) && $_SESSION['user_agent'] === md5($_SERVER['HTTP_USER_AGENT'])) ? $_COOKIE['mobile'] : null;
- if ($m === '1' || ($m === null && $cookie === 'true'))
+ $cookie = isset($_COOKIE['rx_uatype']) ? $_COOKIE['rx_uatype'] : null;
+ $uahash = base64_encode_urlsafe(md5($_SERVER['HTTP_USER_AGENT'], true));
+ if (strncmp($cookie, $uahash . ':', strlen($uahash) + 1) !== 0)
+ {
+ $cookie = null;
+ }
+ elseif ($m === null)
+ {
+ $m = substr($cookie, -1);
+ }
+
+ if ($m === '1')
{
self::$_ismobile = TRUE;
}
- elseif ($m === '0' || ($m === null && $cookie === 'false'))
+ elseif ($m === '0')
{
self::$_ismobile = FALSE;
}
@@ -60,11 +70,11 @@ class Mobile
}
// Set cookie to prevent recalculation.
- if ($cookie !== (self::$_ismobile ? 'true' : 'false'))
+ $uatype = $uahash . ':' . (self::$_ismobile ? '1' : '0');
+ if ($cookie !== $uatype)
{
- $_SESSION['user_agent'] = md5($_SERVER['HTTP_USER_AGENT']);
- $_COOKIE['mobile'] = self::$_ismobile ? 'true' : 'false';
- setcookie('mobile', $_COOKIE['mobile'], 0, RX_BASEURL);
+ setcookie('rx_uatype', $uatype, 0, RX_BASEURL);
+ $_COOKIE['rx_uatype'] = $uatype;
}
return self::$_ismobile;
diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php
index 23671c859..767c0823a 100644
--- a/classes/module/ModuleHandler.class.php
+++ b/classes/module/ModuleHandler.class.php
@@ -1178,6 +1178,12 @@ class ModuleHandler extends Handler
{
return NULL;
}
+
+ // Populate default properties
+ if($oModule->user === false)
+ {
+ $oModule->user = Context::get('logged_info') ?: new Rhymix\Framework\Helpers\SessionHelper;
+ }
// Load language files for the class
if($module !== 'module')
diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php
index 08cefd9f9..074b06624 100644
--- a/classes/module/ModuleObject.class.php
+++ b/classes/module/ModuleObject.class.php
@@ -26,6 +26,20 @@ class ModuleObject extends Object
var $module_config = NULL;
var $ajaxRequestMethod = array('XMLRPC', 'JSON');
var $gzhandler_enable = TRUE;
+ var $user = FALSE;
+
+ /**
+ * Constructor
+ *
+ * @param int $error Error code
+ * @param string $message Error message
+ * @return void
+ */
+ function __construct($error = 0, $message = 'success')
+ {
+ $this->user = Context::get('logged_info') ?: new Rhymix\Framework\Helpers\SessionHelper;
+ parent::__construct($error, $message);
+ }
/**
* setter to set the name of module
diff --git a/classes/template/TemplateHandler.class.php b/classes/template/TemplateHandler.class.php
index cf0f2121f..1dd8364cf 100644
--- a/classes/template/TemplateHandler.class.php
+++ b/classes/template/TemplateHandler.class.php
@@ -20,6 +20,11 @@ class TemplateHandler
private $skipTags = NULL;
private $handler_mtime = 0;
private static $rootTpl = NULL;
+
+ /**
+ * Context variables accessible as $this in template files
+ */
+ public $user = FALSE;
/**
* constructor
@@ -29,6 +34,7 @@ class TemplateHandler
{
$this->config = new stdClass;
$this->handler_mtime = filemtime(__FILE__);
+ $this->user = Context::get('logged_info') ?: new Rhymix\Framework\Helpers\SessionHelper;
}
/**
@@ -818,7 +824,7 @@ class TemplateHandler
}
return preg_replace_callback('@(? true,
+ 'auto_login' => true,
'errorlogger' => true,
'fix_mysql_utf8' => true,
'member_communication' => true,
'seo' => true,
+ 'session_shield' => true,
'smartphone' => true,
'zipperupper' => true,
);
diff --git a/common/framework/datetime.php b/common/framework/datetime.php
index e5deac5c2..66511d2ba 100644
--- a/common/framework/datetime.php
+++ b/common/framework/datetime.php
@@ -62,9 +62,9 @@ class DateTime
*/
public static function getTimezoneForCurrentUser()
{
- if (isset($_SESSION['timezone']) && $_SESSION['timezone'])
+ if (isset($_SESSION['RHYMIX']['timezone']) && $_SESSION['RHYMIX']['timezone'])
{
- return $_SESSION['timezone'];
+ return $_SESSION['RHYMIX']['timezone'];
}
elseif ($default = Config::get('locale.default_timezone'))
{
diff --git a/common/framework/helpers/sessionhelper.php b/common/framework/helpers/sessionhelper.php
new file mode 100644
index 000000000..0216189a9
--- /dev/null
+++ b/common/framework/helpers/sessionhelper.php
@@ -0,0 +1,101 @@
+getMemberInfoByMemberSrl($member_srl);
+ if (intval($member_info->member_srl) === $member_srl)
+ {
+ foreach (get_object_vars($member_info) as $key => $value)
+ {
+ $this->{$key} = $value;
+ }
+ $this->member_srl = $member_srl;
+ $this->group_list = $oMemberModel->getMemberGroups($member_srl);
+ }
+ }
+ }
+
+ /**
+ * Check if this user is a member.
+ *
+ * @return bool
+ */
+ public function isMember()
+ {
+ return $this->member_srl > 0;
+ }
+
+ /**
+ * Check if this user is an administrator.
+ *
+ * @return bool
+ */
+ public function isAdmin()
+ {
+ return $this->is_admin === 'Y';
+ }
+
+ /**
+ * Check if this user is an administrator of a module.
+ *
+ * @param int $module_srl (optional)
+ * @return bool
+ */
+ public function isModuleAdmin($module_srl = null)
+ {
+ return $this->is_admin === 'Y' || getModel('module')->isModuleAdmin($this, $module_srl);
+ }
+
+ /**
+ * Check if this user is valid (not denied or limited).
+ *
+ * @return bool
+ */
+ public function isValid()
+ {
+ if ($this->denied === 'N' && (!$this->limit_date || substr($this->limit_date, 0, 8) < date('Ymd')))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Get the list of groups that this user belongs to.
+ *
+ * @return array
+ */
+ public function getGroups()
+ {
+ return $this->group_list;
+ }
+}
diff --git a/common/framework/security.php b/common/framework/security.php
index 60938a765..f0eb05fb3 100644
--- a/common/framework/security.php
+++ b/common/framework/security.php
@@ -153,7 +153,7 @@ class Security
* @param string $format
* @return string
*/
- public function getRandom($length = 32, $format = 'alnum')
+ public static function getRandom($length = 32, $format = 'alnum')
{
// Find out how many bytes of entropy we really need.
switch($format)
diff --git a/common/framework/session.php b/common/framework/session.php
new file mode 100644
index 000000000..e52df455b
--- /dev/null
+++ b/common/framework/session.php
@@ -0,0 +1,999 @@
+ $val)
+ {
+ if ($key !== 'RHYMIX')
+ {
+ $_SESSION[$key] = $val;
+ }
+ }
+ return true;
+ }
+
+ // Return false if nothing needed to be done.
+ return false;
+ }
+
+ /**
+ * Check if this session needs to be shared with another site with SSO.
+ *
+ * This method uses more or less the same logic as XE's SSO mechanism.
+ * It may need to be changed to a more secure mechanism later.
+ *
+ * @return bool
+ */
+ public static function checkSSO()
+ {
+ // Abort if SSO is disabled, the visitor is a robot, or this is not a typical GET request.
+ if ($_SERVER['REQUEST_METHOD'] !== 'GET' || !config('use_sso') || UA::isRobot() || in_array(\Context::get('act'), array('rss', 'atom')))
+ {
+ return false;
+ }
+
+ // Abort of the default URL is not set.
+ $default_url = \Context::getDefaultUrl();
+ if (!$default_url)
+ {
+ return false;
+ }
+
+ // Get the current site information.
+ $current_url = URL::getCurrentURL();
+ $current_host = parse_url($current_url, \PHP_URL_HOST);
+ $default_host = parse_url($default_url, \PHP_URL_HOST);
+
+ // Step 1: if the current site is not the default site, send SSO validation request to the default site.
+ if($default_host !== $current_host && !\Context::get('sso_response') && $_COOKIE['sso'] !== md5($current_host))
+ {
+ // Set sso cookie to prevent multiple simultaneous SSO validation requests.
+ setcookie('sso', md5($current_host), 0, '/');
+
+ // Redirect to the default site.
+ $sso_request = Security::encrypt($current_url);
+ header('Location:' . URL::modifyURL($default_url, array('sso_request' => $sso_request)));
+ return true;
+ }
+
+ // Step 2: receive and process SSO validation request at the default site.
+ if($default_host === $current_host && \Context::get('sso_request'))
+ {
+ // Get the URL of the origin site
+ $sso_request = Security::decrypt(\Context::get('sso_request'));
+ if (!$sso_request || !preg_match('!^https?://!', $sso_request))
+ {
+ \Context::displayErrorPage('SSO Error', 'Invalid SSO Request', 400);
+ return true;
+ }
+
+ // Redirect back to the origin site.
+ $sso_response = Security::encrypt(session_id());
+ header('Location: ' . URL::modifyURL($sso_request, array('sso_response' => $sso_response)));
+ return true;
+ }
+
+ // Step 3: back at the origin site, set session ID to be the same as the default site.
+ if($default_host !== $current_host && \Context::get('sso_response'))
+ {
+ // Check SSO response
+ $sso_response = Security::decrypt(\Context::get('sso_response'));
+ if ($sso_response === false)
+ {
+ \Context::displayErrorPage('SSO Error', 'Invalid SSO Response', 400);
+ return true;
+ }
+
+ // Check that the response was given by the default site (to prevent session fixation CSRF).
+ if(isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], $default_url) !== 0)
+ {
+ \Context::displayErrorPage('SSO Error', 'Invalid SSO Response', 400);
+ return true;
+ }
+
+ // Set session ID.
+ self::close();
+ session_id($sso_response);
+ self::start();
+
+ // Finally, redirect to the originally requested URL.
+ header('Location: ' . URL::getCurrentURL(array('sso_response' => null)));
+ return true;
+ }
+
+ // If none of the conditions above apply, proceed normally.
+ return false;
+ }
+
+ /**
+ * Create the data structure for a new Rhymix session.
+ *
+ * This method is called automatically by start() when needed.
+ *
+ * @return bool
+ */
+ public static function create()
+ {
+ // Create the data structure for a new Rhymix session.
+ $_SESSION['RHYMIX'] = array();
+ $_SESSION['RHYMIX']['login'] = false;
+ $_SESSION['RHYMIX']['last_login'] = 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();
+ $_SESSION['RHYMIX']['timezone'] = DateTime::getTimezoneForCurrentUser();
+ $_SESSION['RHYMIX']['secret'] = Security::getRandom(32, 'alnum');
+ $_SESSION['RHYMIX']['tokens'] = array();
+ $_SESSION['is_logged'] = false;
+ $_SESSION['is_admin'] = '';
+
+ // Ensure backward compatibility with XE session.
+ $member_srl = isset($_SESSION['member_srl']) ? ($_SESSION['member_srl'] ?: false) : false;
+ if ($member_srl && self::isValid($member_srl))
+ {
+ self::login($member_srl, false);
+ }
+ else
+ {
+ $_SESSION['member_srl'] = false;
+ }
+
+ // Try autologin.
+ if (!$member_srl && self::$_autologin_key)
+ {
+ $member_srl = getController('member')->doAutologin(self::$_autologin_key);
+ if ($member_srl && self::isValid($member_srl))
+ {
+ self::login($member_srl, false);
+ }
+ else
+ {
+ self::destroyAutologinKeys();
+ }
+ }
+
+ // Pass control to refresh() to generate security keys.
+ return self::refresh();
+ }
+
+ /**
+ * Refresh the session.
+ *
+ * This method can be used to invalidate old session cookies.
+ * It is called automatically when someone logs in or out.
+ *
+ * @return bool
+ */
+ public static function refresh()
+ {
+ // Get session parameters.
+ list($lifetime, $refresh_interval, $domain, $path) = self::_getParams();
+
+ // Set the domain initialization timestamp.
+ if (!isset($_SESSION['RHYMIX']['keys'][$domain]['started']))
+ {
+ $_SESSION['RHYMIX']['keys'][$domain]['started'] = time();
+ }
+
+ // Reset the trusted information.
+ if (!isset($_SESSION['RHYMIX']['keys'][$domain]['trusted']))
+ {
+ $_SESSION['RHYMIX']['keys'][$domain]['trusted'] = 0;
+ }
+
+ // Create or refresh the HTTP-only key.
+ if (isset($_SESSION['RHYMIX']['keys'][$domain]['key1']))
+ {
+ $_SESSION['RHYMIX']['keys'][$domain]['key1_prev'] = $_SESSION['RHYMIX']['keys'][$domain]['key1'];
+ }
+ $_SESSION['RHYMIX']['keys'][$domain]['key1'] = Security::getRandom(24, 'alnum');
+ $_SESSION['RHYMIX']['keys'][$domain]['key1_time'] = time();
+
+ // Create or refresh the HTTPS-only key.
+ if (\RX_SSL)
+ {
+ if (isset($_SESSION['RHYMIX']['keys'][$domain]['key2']))
+ {
+ $_SESSION['RHYMIX']['keys'][$domain]['key2_prev'] = $_SESSION['RHYMIX']['keys'][$domain]['key2'];
+ }
+ $_SESSION['RHYMIX']['keys'][$domain]['key2'] = Security::getRandom(24, 'alnum');
+ $_SESSION['RHYMIX']['keys'][$domain]['key2_time'] = time();
+ }
+
+ // Pass control to _setKeys() to send the keys to the client.
+ return self::_setKeys();
+ }
+
+ /**
+ * Close the session and write its data.
+ *
+ * This method is called automatically at the end of a request, but you can
+ * call it sooner if you don't plan to write any more data to the session.
+ *
+ * @return bool
+ */
+ public static function close()
+ {
+ self::$_started = false;
+ session_write_close();
+ }
+
+ /**
+ * Destroy the session.
+ *
+ * This method deletes all data associated with the current session.
+ *
+ * @return bool
+ */
+ public static function destroy()
+ {
+ unset($_SESSION['RHYMIX']);
+ self::$_started = false;
+ self::$_member_info = false;
+ self::_setKeys();
+ self::destroyAutologinKeys();
+ @session_destroy();
+ return true;
+ }
+
+ /**
+ * Log in.
+ *
+ * This method accepts either an integer or a member object.
+ * It returns true on success and false on failure.
+ *
+ * @param int $member_srl
+ * @param bool $refresh (optional)
+ * @return bool
+ */
+ public static function login($member_srl, $refresh = true)
+ {
+ // Check the validity of member_srl.
+ if (is_object($member_srl) && isset($member_srl->member_srl))
+ {
+ $member_srl = $member_srl->member_srl;
+ }
+ if ($member_srl < 1)
+ {
+ return false;
+ }
+
+ // Set member_srl to session.
+ $_SESSION['RHYMIX']['login'] = $_SESSION['member_srl'] = $member_srl;
+ $_SESSION['RHYMIX']['last_login'] = time();
+ $_SESSION['is_logged'] = (bool)$member_srl;
+ self::$_member_info = false;
+
+ // Refresh the session keys.
+ if ($refresh)
+ {
+ return self::refresh();
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ /**
+ * Log out.
+ *
+ * This method returns true on success and false on failure.
+ *
+ * @return bool
+ */
+ public static function logout()
+ {
+ $_SESSION['RHYMIX']['login'] = $_SESSION['member_srl'] = false;
+ $_SESSION['RHYMIX']['last_login'] = false;
+ $_SESSION['is_logged'] = false;
+ self::$_member_info = false;
+ return self::destroy();
+ }
+
+ /**
+ * Check if the session has been started.
+ *
+ * @return bool
+ */
+ public static function isStarted()
+ {
+ return self::$_started;
+ }
+
+ /**
+ * Check if a member has logged in with this session.
+ *
+ * This method returns true or false, not 'Y' or 'N'.
+ *
+ * @return bool
+ */
+ public static function isMember()
+ {
+ return ($_SESSION['member_srl'] > 0 && $_SESSION['RHYMIX']['login'] > 0);
+ }
+
+ /**
+ * Check if an administrator is logged in with this session.
+ *
+ * This method returns true or false, not 'Y' or 'N'.
+ *
+ * @return bool
+ */
+ public static function isAdmin()
+ {
+ $member_info = self::getMemberInfo();
+ return ($member_info && $member_info->is_admin === 'Y');
+ }
+
+ /**
+ * Check if the current session is trusted.
+ *
+ * This can be useful if you want to force a password check before granting
+ * access to certain pages. The duration of trust can be set by calling
+ * the Session::setTrusted() method.
+ *
+ * @return bool
+ */
+ public static function isTrusted()
+ {
+ // Get session parameters.
+ list($lifetime, $refresh_interval, $domain, $path) = self::_getParams();
+
+ // Check the 'trusted' parameter.
+ if ($_SESSION['RHYMIX']['keys'][$domain]['trusted'] > time())
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Check if the current session is valid for a given member_srl.
+ *
+ * The session can be invalidated by password changes and other user action.
+ *
+ * @param int $member_srl (optional)
+ * @return bool
+ */
+ public static function isValid($member_srl = null)
+ {
+ // If no member_srl is given, the session is always valid.
+ $member_srl = intval($member_srl) ?: (isset($_SESSION['RHYMIX']['login']) ? $_SESSION['RHYMIX']['login'] : 0);
+ if (!$member_srl)
+ {
+ 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)
+ {
+ return false;
+ }
+
+ // Check member information to see if denied or limited.
+ $member_info = getModel('member')->getMemberInfoByMemberSrl($member_srl);
+ if ($member_info->denied === 'Y')
+ {
+ return false;
+ }
+ if ($member_info->limit_date && substr($member_info->limit_date, 0, 8) >= date('Ymd'))
+ {
+ return false;
+ }
+
+ // Return true if all checks have passed.
+ return true;
+ }
+
+ /**
+ * Get the member_srl of the currently logged in member.
+ *
+ * This method returns an integer, or false if nobody is logged in.
+ *
+ * @return int|false
+ */
+ public static function getMemberSrl()
+ {
+ return $_SESSION['member_srl'] ?: ($_SESSION['RHYMIX']['login'] ?: false);
+ }
+
+ /**
+ * Get information about the currently logged in member.
+ *
+ * This method returns an object, or false if nobody is logged in.
+ *
+ * @return object|false
+ */
+ public static function getMemberInfo()
+ {
+ // Return false if the current user is not logged in.
+ $member_srl = self::getMemberSrl();
+ if (!$member_srl)
+ {
+ return false;
+ }
+
+ // Create a member info object.
+ if (!self::$_member_info || self::$_member_info->member_srl != $member_srl)
+ {
+ self::$_member_info = new Helpers\SessionHelper($member_srl);
+ }
+
+ // Return the member info object.
+ return self::$_member_info->member_srl ? self::$_member_info : false;
+ }
+
+ /**
+ * Set the member info.
+ *
+ * This method is for debugging and testing purposes only.
+ *
+ * @param object $member_info
+ * @return void
+ */
+ public static function setMemberInfo($member_info)
+ {
+ self::$_member_info = $member_info;
+ }
+
+ /**
+ * Get the current user's preferred language.
+ *
+ * If the current user does not have a preferred language, this method
+ * will return the default language.
+ *
+ * @return string
+ */
+ public static function getLanguage()
+ {
+ return isset($_SESSION['RHYMIX']['language']) ? $_SESSION['RHYMIX']['language'] : \Context::getLangType();
+ }
+
+ /**
+ * Set the current user's preferred language.
+ *
+ * @param string $language
+ * @return bool
+ */
+ public static function setLanguage($language)
+ {
+ $_SESSION['RHYMIX']['language'] = $language;
+ }
+
+ /**
+ * Get the current user's preferred time zone.
+ *
+ * If the current user does not have a preferred time zone, this method
+ * will return the default time zone for display.
+ *
+ * @return string
+ */
+ public static function getTimezone()
+ {
+ return DateTime::getTimezoneForCurrentUser();
+ }
+
+ /**
+ * Set the current user's preferred time zone.
+ *
+ * @param string $timezone
+ * @return bool
+ */
+ public static function setTimezone($timezone)
+ {
+ $_SESSION['RHYMIX']['timezone'] = $timezone;
+ }
+
+ /**
+ * Mark the current session as trusted for a given duration.
+ *
+ * See isTrusted() for description.
+ *
+ * @param int $duration (optional, default is 300 seconds)
+ * @return bool
+ */
+ public static function setTrusted($duration = 300)
+ {
+ // Get session parameters.
+ list($lifetime, $refresh_interval, $domain, $path) = self::_getParams();
+
+ // Update the 'trusted' parameter if the current user is logged in.
+ if (isset($_SESSION['RHYMIX']['keys'][$domain]) && $_SESSION['RHYMIX']['login'])
+ {
+ $_SESSION['RHYMIX']['keys'][$domain]['trusted'] = time() + $duration;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Create a token that can only be verified in the same session.
+ *
+ * This can be used to create CSRF tokens, etc.
+ * If you specify a key, the same key must be used to verify the token.
+ *
+ * @param string $key (optional)
+ * @return string
+ */
+ public static function createToken($key = null)
+ {
+ $token = Security::getRandom(16, 'alnum');
+ $_SESSION['RHYMIX']['tokens'][$token] = strval($key);
+ return $token;
+ }
+
+ /**
+ * Verify a token.
+ *
+ * This method returns true if the token is valid, and false otherwise.
+ *
+ * @param string $token
+ * @param string $key (optional)
+ * @return bool
+ */
+ public static function verifyToken($token, $key = null)
+ {
+ if (isset($_SESSION['RHYMIX']['tokens'][$token]) && $_SESSION['RHYMIX']['tokens'][$token] === strval($key))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Invalidate a token so that it cannot be verified.
+ *
+ * @param string $token
+ * @param string $key (optional)
+ * @return bool
+ */
+ public static function invalidateToken($token)
+ {
+ if (isset($_SESSION['RHYMIX']['tokens'][$token]))
+ {
+ unset($_SESSION['RHYMIX']['tokens'][$token]);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Encrypt data so that it can only be decrypted in the same session.
+ *
+ * Arrays and objects can also be encrypted. (They will be serialized.)
+ * Resources and the boolean false value will not be preserved.
+ *
+ * @param mixed $plaintext
+ * @return string
+ */
+ public static function encrypt($plaintext)
+ {
+ $key = $_SESSION['RHYMIX']['secret'] . Config::get('crypto.encryption_key');
+ return Security::encrypt($plaintext, $key);
+ }
+
+ /**
+ * Decrypt data that was encrypted in the same session.
+ *
+ * This method returns the decrypted data, or false on failure.
+ * All users of this method must be designed to handle failures safely.
+ *
+ * @param string $ciphertext
+ * @return mixed
+ */
+ public static function decrypt($ciphertext)
+ {
+ $key = $_SESSION['RHYMIX']['secret'] . Config::get('crypto.encryption_key');
+ return Security::decrypt($ciphertext, $key);
+ }
+
+ /**
+ * Get session parameters.
+ *
+ * @return array
+ */
+ protected static function _getParams()
+ {
+ $lifetime = Config::get('session.lifetime');
+ $refresh = Config::get('session.refresh') ?: 300;
+ $domain = Config::get('session.domain') ?: (ini_get('session.cookie_domain') ?: preg_replace('/:\\d+$/', '', $_SERVER['HTTP_HOST']));
+ $path = Config::get('session.path') ?: ini_get('session.cookie_path');
+ return array($lifetime, $refresh, $domain, $path);
+ }
+
+ /**
+ * Get session keys.
+ *
+ * @return array
+ */
+ protected static function _getKeys()
+ {
+ // Initialize keys.
+ $key1 = $key2 = $key3 = null;
+
+ // Fetch and validate the HTTP-only key.
+ if (isset($_COOKIE['rx_sesskey1']) && ctype_alnum($_COOKIE['rx_sesskey1']) && strlen($_COOKIE['rx_sesskey1']) === 24)
+ {
+ $key1 = $_COOKIE['rx_sesskey1'];
+ }
+
+ // Fetch and validate the HTTPS-only key.
+ if (isset($_COOKIE['rx_sesskey2']) && ctype_alnum($_COOKIE['rx_sesskey2']) && strlen($_COOKIE['rx_sesskey2']) === 24)
+ {
+ $key2 = $_COOKIE['rx_sesskey2'];
+ }
+
+ // Fetch and validate the autologin key.
+ if (isset($_COOKIE['rx_autologin']) && ctype_alnum($_COOKIE['rx_autologin']) && strlen($_COOKIE['rx_autologin']) === 48)
+ {
+ $key3 = $_COOKIE['rx_autologin'];
+ }
+
+ return array($key1, $key1 === null ? null : $key2, $key3);
+ }
+
+ /**
+ * Set session keys.
+ *
+ * @return bool
+ */
+ protected static function _setKeys()
+ {
+ // Get session parameters.
+ list($lifetime, $refresh_interval, $domain, $path) = self::_getParams();
+ $lifetime = $lifetime ? ($lifetime + time()) : 0;
+
+ // Set or destroy the HTTP-only key.
+ if (isset($_SESSION['RHYMIX']['keys'][$domain]['key1']))
+ {
+ setcookie('rx_sesskey1', $_SESSION['RHYMIX']['keys'][$domain]['key1'], $lifetime, $path, $domain, false, true);
+ $_COOKIE['rx_sesskey1'] = $_SESSION['RHYMIX']['keys'][$domain]['key1'];
+ }
+ else
+ {
+ setcookie('rx_sesskey1', 'deleted', time() - 86400, $path, $domain, false, true);
+ unset($_COOKIE['rx_sesskey1']);
+ }
+
+ // Set or delete the HTTPS-only key.
+ if (\RX_SSL && isset($_SESSION['RHYMIX']['keys'][$domain]['key2']))
+ {
+ setcookie('rx_sesskey2', $_SESSION['RHYMIX']['keys'][$domain]['key2'], $lifetime, $path, $domain, true, true);
+ $_COOKIE['rx_sesskey2'] = $_SESSION['RHYMIX']['keys'][$domain]['key2'];
+ }
+
+ return true;
+ }
+
+ /**
+ * Set autologin key.
+ *
+ * @param string $autologin_key
+ * @param string $security_key
+ * @return bool
+ */
+ public static function setAutologinKeys($autologin_key, $security_key)
+ {
+ // Get session parameters.
+ list($lifetime, $refresh_interval, $domain, $path) = self::_getParams();
+ $lifetime = time() + (86400 * 365);
+
+ // Set or destroy the HTTP-only key.
+ if ($autologin_key && $security_key)
+ {
+ setcookie('rx_autologin', $autologin_key . $security_key, $lifetime, $path, $domain, false, true);
+ $_COOKIE['rx_autologin'] = $autologin_key . $security_key;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Destroy autologin keys.
+ *
+ * @return bool
+ */
+ public static function destroyAutologinKeys()
+ {
+ if (self::$_autologin_key)
+ {
+ executeQuery('member.deleteAutologin', (object)array('autologin_key' => substr(self::$_autologin_key, 0, 24)));
+ self::$_autologin_key = false;
+ $result = true;
+ }
+ else
+ {
+ $result = false;
+ }
+
+ setcookie('rx_autologin', 'deleted', time() - 86400, $path, $domain, false, true);
+ unset($_COOKIE['rx_autologin']);
+ return $result;
+ }
+
+ /**
+ * Destroy all other autologin keys (except the current session).
+ *
+ * @param int $member_srl
+ * @return bool
+ */
+ public static function destroyOtherSessions($member_srl)
+ {
+ // Check the validity of member_srl.
+ $member_srl = intval($member_srl);
+ if (!$member_srl)
+ {
+ return false;
+ }
+
+ // 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;
+ }
+ else
+ {
+ return false;
+ }
+
+ // Destroy all other autologin keys.
+ if (self::$_autologin_key)
+ {
+ executeQuery('member.deleteAutologin', (object)array('member_srl' => $member_srl, 'not_autologin_key' => substr(self::$_autologin_key, 0, 24)));
+ }
+ else
+ {
+ executeQuery('member.deleteAutologin', (object)array('member_srl' => $member_srl));
+ }
+
+ return true;
+ }
+}
diff --git a/modules/admin/lang/en.php b/modules/admin/lang/en.php
index d4cd280ab..e57508c64 100644
--- a/modules/admin/lang/en.php
+++ b/modules/admin/lang/en.php
@@ -67,10 +67,12 @@ $lang->msg_blacklisted_addon = 'This addon has been disabled because it conflict
$lang->msg_blacklisted_widget = 'This widget has been disabled because it conflicts with a feature that Rhymix supports by default, or is known to have other compatibility problems.';
$lang->msg_blacklisted_layout = 'This layout has been disabled because it conflicts with a feature that Rhymix supports by default, or is known to have other compatibility problems.';
$lang->msg_blacklisted_reason['autolang'] = 'Similar functionality can be configured in the System Settings page.';
+$lang->msg_blacklisted_reason['auto_login'] = 'The functionality that this module used to provide is included by default in Rhymix.';
$lang->msg_blacklisted_reason['errorlogger'] = 'Similar functionality can be configured in the Debug Settings page.';
$lang->msg_blacklisted_reason['fix_mysql_utf8'] = 'The functionality that this addon used to provide is included by default in Rhymix.';
$lang->msg_blacklisted_reason['member_communication'] = 'The functionality that this addon used to provide has been moved to the member and ncenterlite modules.';
$lang->msg_blacklisted_reason['seo'] = 'Similar functionality can be configured in the SEO Settings page.';
+$lang->msg_blacklisted_reason['session_shield'] = 'The functionality that this addon used to provide is included by default in Rhymix.';
$lang->msg_blacklisted_reason['smartphone'] = 'This module was disabled in XE long before Rhymix even existed.';
$lang->msg_blacklisted_reason['zipperupper'] = 'Similar functionality can be configured in the Advanced Settings page.';
$lang->msg_warning = 'Warning';
diff --git a/modules/admin/lang/ko.php b/modules/admin/lang/ko.php
index 7841e1630..c90915f14 100644
--- a/modules/admin/lang/ko.php
+++ b/modules/admin/lang/ko.php
@@ -67,10 +67,12 @@ $lang->msg_blacklisted_addon = '이 애드온은 Rhymix에서 기본 제공하
$lang->msg_blacklisted_widget = '이 위젯은 Rhymix에서 기본 제공하는 기능과 충돌하거나 그 밖의 호환성 문제가 있으므로 사용이 중단되었습니다.';
$lang->msg_blacklisted_layout = '이 레이아웃은 Rhymix에서 기본 제공하는 기능과 충돌하거나 그 밖의 호환성 문제가 있으므로 사용이 중단되었습니다.';
$lang->msg_blacklisted_reason['autolang'] = '이 애드온에서 제공하던 기능은 시스템 설정 페이지에서 관리할 수 있습니다.';
+$lang->msg_blacklisted_reason['auto_login'] = '이 모듈에서 제공하던 기능은 Rhymix에 포함되어 있습니다.';
$lang->msg_blacklisted_reason['errorlogger'] = '이 모듈에서 제공하던 기능은 디버그 설정 페이지에서 관리할 수 있습니다.';
$lang->msg_blacklisted_reason['fix_mysql_utf8'] = '이 애드온에서 제공하던 기능은 Rhymix에 포함되어 있습니다.';
$lang->msg_blacklisted_reason['member_communication'] = '이 애드온에서 제공하던 기능은 알림센터 모듈에서 관리할 수 있습니다.';
$lang->msg_blacklisted_reason['seo'] = '이 모듈에서 제공하던 기능은 SEO 설정 페이지에서 관리할 수 있습니다.';
+$lang->msg_blacklisted_reason['session_shield'] = '이 애드온에서 제공하던 기능은 Rhymix에 포함되어 있습니다.';
$lang->msg_blacklisted_reason['smartphone'] = '이 모듈은 XE에서도 사용되지 않고 있었습니다.';
$lang->msg_blacklisted_reason['zipperupper'] = '이 애드온에서 제공하던 기능은 고급 설정 페이지에서 관리할 수 있습니다.';
$lang->msg_warning = '경고';
diff --git a/modules/member/conf/module.xml b/modules/member/conf/module.xml
index 0154c5404..27baf50b7 100644
--- a/modules/member/conf/module.xml
+++ b/modules/member/conf/module.xml
@@ -16,6 +16,7 @@
Spam user management. This function denied user $lang->btn_spammer_delete_all = 'Delete all'; $lang->spammer_move_to_trash = 'Move to trash'; $lang->msg_spammer_complete = 'Completed.'; +$lang->cmd_login_browser_info = 'Browser Information'; +$lang->cmd_initial_login = 'First Login'; +$lang->cmd_recent_visit = 'Recent Visit'; diff --git a/modules/member/lang/ko.php b/modules/member/lang/ko.php index d7f774352..a7b604339 100644 --- a/modules/member/lang/ko.php +++ b/modules/member/lang/ko.php @@ -109,6 +109,7 @@ $lang->cmd_manage_email_host = '이메일 제공자 관리'; $lang->cmd_manage_nick_name = '금지 닉네임 관리'; $lang->cmd_manage_form = '가입 폼 관리'; $lang->cmd_view_own_document = '작성 글 보기'; +$lang->cmd_view_active_logins = '로그인 관리'; $lang->cmd_manage_member_info = '회원 정보 관리'; $lang->cmd_trace_document = '작성글 추적'; $lang->cmd_trace_comment = '작성 댓글 추적'; @@ -169,6 +170,7 @@ $lang->cmd_config_password_strength = '비밀번호 보안수준'; $lang->cmd_password_hashing_algorithm = '비밀번호 암호화 알고리듬'; $lang->cmd_password_hashing_work_factor = '비밀번호 암호화 소요시간'; $lang->cmd_password_hashing_auto_upgrade = '알고리듬 자동 업그레이드'; +$lang->cmd_password_change_invalidate_other_sessions = '비번 변경시 다른 기기 로그아웃'; $lang->password_strength_low = '낮음'; $lang->password_strength_normal = '보통'; $lang->password_strength_high = '높음'; @@ -176,6 +178,7 @@ $lang->about_password_strength_config = '회원들이 비밀번호를 등록/변 $lang->about_password_hashing_algorithm = '회원들의 비밀번호를 DB에 저장할 때 암호화(해싱)하는 방식을 지정할 수 있습니다.'; $lang->about_password_hashing_work_factor = '시간이 오래 걸리는 알고리듬일수록 보안이 강하지만, 로그인이 오래 걸릴 수 있습니다. bcrypt 및 pbkdf2 알고리듬에만 적용됩니다.'; $lang->about_password_hashing_auto_upgrade = '설정된 알고리듬과 다른 방법으로 암호화된 비밀번호가 있으면 다음 로그인시 설정된 알고리듬으로 자동 변환합니다.'; +$lang->about_password_change_invalidate_other_sessions = '비밀번호를 변경하면 현재 기기(브라우저)를 제외한 모든 로그인이 풀리도록 합니다.'; $lang->about_password_strength['low'] = '비밀번호는 4자 이상이어야 합니다.'; $lang->about_password_strength['normal'] = '비밀번호는 6자리 이상이어야 하며 영문과 숫자를 반드시 포함해야 합니다.'; $lang->about_password_strength['high'] = '비밀번호는 8자리 이상이어야 하며 영문과 숫자, 특수문자를 반드시 포함해야 합니다.'; @@ -317,3 +320,6 @@ $lang->spammer_move_to_trash = '휴지통으로 이동'; $lang->msg_spammer_complete = '완료되었습니다.'; $lang->nick_name_before_changing = '닉네임 변경 전'; $lang->nick_name_after_changing = '닉네임 변경 후'; +$lang->cmd_login_browser_info = '브라우저 정보'; +$lang->cmd_initial_login = '최초 로그인'; +$lang->cmd_recent_visit = '최근 방문'; diff --git a/modules/member/member.admin.controller.php b/modules/member/member.admin.controller.php index 70ff7e575..e34edaa74 100644 --- a/modules/member/member.admin.controller.php +++ b/modules/member/member.admin.controller.php @@ -171,6 +171,7 @@ class memberAdminController extends member 'password_hashing_algorithm', 'password_hashing_work_factor', 'password_hashing_auto_upgrade', + 'password_change_invalidate_other_sessions', 'update_nickname_log', 'member_allow_fileupload' ); diff --git a/modules/member/member.class.php b/modules/member/member.class.php index c2a59818e..31ae1df4e 100644 --- a/modules/member/member.class.php +++ b/modules/member/member.class.php @@ -204,6 +204,9 @@ class member extends ModuleObject { if(!$oDB->isColumnExists("member", "list_order")) return true; if(!$oDB->isIndexExists("member","idx_list_order")) return true; + + // Check autologin table + if(!$oDB->isColumnExists("member_autologin", "security_key")) return true; $oModuleModel = getModel('module'); $config = $oModuleModel->getModuleConfig('member'); @@ -314,6 +317,13 @@ class member extends ModuleObject { { $oDB->addIndex("member","idx_list_order", array("list_order")); } + + // Check autologin table + if(!$oDB->isColumnExists("member_autologin", "security_key")) + { + $oDB->dropTable('member_autologin'); + $oDB->createTableByXmlFile($this->module_path . '/schemas/member_autologin.xml'); + } $oModuleModel = getModel('module'); $config = $oModuleModel->getModuleConfig('member'); diff --git a/modules/member/member.controller.php b/modules/member/member.controller.php index f0a492dca..b20d1f6a4 100644 --- a/modules/member/member.controller.php +++ b/modules/member/member.controller.php @@ -101,7 +101,9 @@ class memberController extends member if(!$trigger_output->toBool()) return $trigger_output; // Destroy session information + Rhymix\Framework\Session::logout(); $this->destroySessionInfo(); + $this->_clearMemberCache($logged_info->member_srl); // Call a trigger after log-out (after) ModuleHandler::triggerCall('member.doLogout', 'after', $logged_info); @@ -111,9 +113,9 @@ class memberController extends member $oModuleModel = getModel('module'); $config = $oModuleModel->getModuleConfig('member'); if($config->after_logout_url) + { $output->redirect_url = $config->after_logout_url; - - $this->_clearMemberCache($logged_info->member_srl); + } return $output; } @@ -202,6 +204,52 @@ class memberController extends member $oDocumentController = getController('document'); $oDocumentController->deleteDocument($document_srl, true); } + + /** + * Delete an autologin + */ + function procMemberDeleteAutologin() + { + // Check login information + if(!Context::get('is_logged')) return new Object(-1, 'msg_not_logged'); + $logged_info = Context::get('logged_info'); + + $autologin_id = intval(Context::get('autologin_id')); + $autologin_key = Context::get('autologin_key'); + if (!$autologin_id || !$autologin_key) + { + return new Object(-1, 'msg_invalid_request'); + } + + $args = new stdClass; + $args->autologin_id = $autologin_id; + $args->autologin_key = $autologin_key; + $output = executeQueryArray('member.getAutologin', $args); + if ($output->toBool() && $output->data) + { + $autologin_info = array_first($output->data); + if ($autologin_info->member_srl == $logged_info->member_srl) + { + $output = executeQuery('member.deleteAutologin', $args); + if ($output->toBool()) + { + $this->add('deleted', 'Y'); + } + else + { + $this->add('deleted', 'N'); + } + } + else + { + $this->add('deleted', 'N'); + } + } + else + { + $this->add('deleted', 'N'); + } + } /** * Check values when member joining @@ -687,6 +735,14 @@ class memberController extends member $args->password = $password; $output = $this->updateMemberPassword($args); if(!$output->toBool()) return $output; + + // Log out all other sessions. + $oModuleModel = getModel('module'); + $member_config = $oModuleModel->getModuleConfig('member'); + if ($member_config->password_change_invalidate_other_sessions === 'Y') + { + Rhymix\Framework\Session::destroyOtherSessions($member_srl); + } $this->add('member_srl', $args->member_srl); $this->setMessage('success_updated'); @@ -723,7 +779,8 @@ class memberController extends member $output = $this->deleteMember($member_srl); if(!$output->toBool()) return $output; // Destroy all session information - $this->destroySessionInfo(); + executeQuery('member.deleteAutologin', (object)array('member_srl' => $member_srl)); + Rhymix\Framework\Session::logout(); // Return success message $this->setMessage('success_leaved'); @@ -1621,82 +1678,56 @@ class memberController extends member /** * Auto-login * - * @return void + * @param string $autologin_key + * @return int|false */ - function doAutologin() + function doAutologin($autologin_key = null) { - // Get a key value of auto log-in - $args = new stdClass; - $args->autologin_key = $_COOKIE['xeak']; - // Get information of the key - $output = executeQuery('member.getAutologin', $args); - // If no information exists, delete a cookie - if(!$output->toBool() || !$output->data) + // Validate the key. + if (strlen($autologin_key) == 48) { - setCookie('xeak',null,$_SERVER['REQUEST_TIME']+60*60*24*365, '/'); - return; - } - - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); - - $user_id = ($config->identifier == 'user_id') ? $output->data->user_id : $output->data->email_address; - $password = $output->data->password; - - if(!$user_id || !$password) - { - setCookie('xeak',null,$_SERVER['REQUEST_TIME']+60*60*24*365, '/'); - return; - } - - $do_auto_login = false; - - // Compare key values based on the information - $check_key = strtolower($user_id).$password.$_SERVER['HTTP_USER_AGENT']; - $check_key = substr(hash_hmac('sha256', $check_key, substr($args->autologin_key, 0, 32)), 0, 32); - - if($check_key === substr($args->autologin_key, 32)) - { - // Check change_password_date - $oModuleModel = getModel('module'); - $member_config = $oModuleModel->getModuleConfig('member'); - $limit_date = $member_config->change_password_date; - - // Check if change_password_date is set - if($limit_date > 0) - { - $oMemberModel = getModel('member'); - $columnList = array('member_srl', 'change_password_date'); - - if($config->identifier == 'user_id') - { - $member_info = $oMemberModel->getMemberInfoByUserID($user_id, $columnList); - } - else - { - $member_info = $oMemberModel->getMemberInfoByEmailAddress($user_id, $columnList); - } - - if($member_info->change_password_date >= date('YmdHis', strtotime('-'.$limit_date.' day')) ){ - $do_auto_login = true; - } - - } - else - { - $do_auto_login = true; - } - } - - if($do_auto_login) - { - $output = $this->doLogin($user_id); + $security_key = substr($autologin_key, 24, 24); + $autologin_key = substr($autologin_key, 0, 24); } else { - executeQuery('member.deleteAutologin', $args); - setCookie('xeak',null,$_SERVER['REQUEST_TIME']+60*60*24*365, '/'); + return false; } + + // Fetch autologin information from DB. + $args = new stdClass; + $args->autologin_key = $autologin_key; + $output = executeQuery('member.getAutologin', $args); + if (!$output->toBool() || !$output->data) + { + return false; + } + if (is_array($output->data)) + { + $output->data = array_first($output->data); + } + + // Check the security key. + if ($output->data->security_key !== $security_key || !$output->data->member_srl) + { + $args = new stdClass; + $args->autologin_key = $autologin_key; + executeQuery('member.deleteAutologin', $args); + return false; + } + + // Update the security key. + $new_security_key = Rhymix\Framework\Security::getRandom(24, 'alnum'); + $args = new stdClass; + $args->security_key = $new_security_key; + $update_output = executeQuery('member.updateAutologin', $args); + if ($update_output->toBool()) + { + Rhymix\Framework\Session::setAutologinKeys($autologin_key, $new_security_key); + } + + // Return the member_srl. + return intval($output->data->member_srl); } /** @@ -1858,19 +1889,21 @@ class memberController extends member // When user checked to use auto-login if($keep_signed) { - // Key generate for auto login - $random_key = Rhymix\Framework\Security::getRandom(32, 'hex'); - $extra_key = strtolower($user_id).$this->memberInfo->password.$_SERVER['HTTP_USER_AGENT']; - $extra_key = substr(hash_hmac('sha256', $extra_key, $random_key), 0, 32); + $random_key = Rhymix\Framework\Security::getRandom(48, 'alnum'); $autologin_args = new stdClass; - $autologin_args->autologin_key = $random_key.$extra_key; + $autologin_args->autologin_key = substr($random_key, 0, 24); + $autologin_args->security_key = substr($random_key, 24, 24); $autologin_args->member_srl = $this->memberInfo->member_srl; - executeQuery('member.deleteAutologin', $autologin_args); + $autologin_args->user_agent = json_encode(Rhymix\Framework\UA::getBrowserInfo()); $autologin_output = executeQuery('member.insertAutologin', $autologin_args); - if($autologin_output->toBool()) setCookie('xeak',$autologin_args->autologin_key, $_SERVER['REQUEST_TIME']+31536000, '/'); + if ($autologin_output->toBool()) + { + Rhymix\Framework\Session::setAutologinKeys(substr($random_key, 0, 24), substr($random_key, 24, 24)); + } } $this->setSessionInfo(); + Rhymix\Framework\Session::login($this->memberInfo->member_srl); return $output; } @@ -1881,29 +1914,23 @@ class memberController extends member { $oMemberModel = getModel('member'); $config = $oMemberModel->getMemberConfig(); + // If your information came through the current session information to extract information from the users - if(!$this->memberInfo && $_SESSION['member_srl'] && $oMemberModel->isLogged() ) + if(!$this->memberInfo && Rhymix\Framework\Session::getMemberSrl()) { - $this->memberInfo = $oMemberModel->getMemberInfoByMemberSrl($_SESSION['member_srl']); - // If you do not destroy the session Profile - if($this->memberInfo->member_srl != $_SESSION['member_srl']) - { - $this->destroySessionInfo(); - return; - } + $this->memberInfo = Rhymix\Framework\Session::getMemberInfo(); } - // Stop using the session id is destroyed - if($this->memberInfo->denied=='Y') + if(!$this->memberInfo->member_srl) { - $this->destroySessionInfo(); return; } + // Log in for treatment sessions set + /* $_SESSION['is_logged'] = true; - $_SESSION['ipaddress'] = $_SERVER['REMOTE_ADDR']; - $_SESSION['member_srl'] = $this->memberInfo->member_srl; + $_SESSION['member_srl'] = $_SESSION['RHYMIX']['login'] = $this->memberInfo->member_srl; $_SESSION['is_admin'] = ''; - setcookie('xe_logged', 'true', 0, '/'); + */ // Do not save your password in the session jiwojum;; //unset($this->memberInfo->password); // User Group Settings @@ -1927,6 +1954,7 @@ class memberController extends member $this->addMemberMenu( 'dispMemberScrappedDocument', 'cmd_view_scrapped_document'); $this->addMemberMenu( 'dispMemberSavedDocument', 'cmd_view_saved_document'); $this->addMemberMenu( 'dispMemberOwnDocument', 'cmd_view_own_document'); + $this->addMemberMenu( 'dispMemberActiveLogins', 'cmd_view_active_logins'); if($config->update_nickname_log == 'Y') { $this->addMemberMenu( 'dispMemberModifyNicknameLog', 'cmd_modify_nickname_log'); @@ -2618,19 +2646,11 @@ class memberController extends member $_SESSION[$key] = ''; } - session_destroy(); + Rhymix\Framework\Session::destroy(); setcookie(session_name(), '', $_SERVER['REQUEST_TIME']-42000, '/'); setcookie('sso','',$_SERVER['REQUEST_TIME']-42000, '/'); setcookie('xeak','',$_SERVER['REQUEST_TIME']-42000, '/'); setcookie('xe_logged', 'false', $_SERVER['REQUEST_TIME'] - 42000, '/'); - - if($memberSrl || $_COOKIE['xeak']) - { - $args = new stdClass(); - $args->member_srl = $memberSrl; - $args->autologin_key = $_COOKIE['xeak']; - $output = executeQuery('member.deleteAutologin', $args); - } } function _updatePointByGroup($memberSrl, $groupSrlList) diff --git a/modules/member/member.model.php b/modules/member/member.model.php index 91fb434a9..f29729411 100644 --- a/modules/member/member.model.php +++ b/modules/member/member.model.php @@ -231,36 +231,9 @@ class memberModel extends member /** * @brief Check if logged-in */ - function isLogged() { - if($_SESSION['is_logged']) - { - if(Mobile::isFromMobilePhone()) - { - return true; - } - elseif(filter_var($_SESSION['ipaddress'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) - { - // IPv6: require same /48 - if(strncmp(inet_pton($_SESSION['ipaddress']), inet_pton($_SERVER['REMOTE_ADDR']), 6) == 0) - { - return true; - } - } - else - { - // IPv4: require same /24 - if(ip2long($_SESSION['ipaddress']) >> 8 == ip2long($_SERVER['REMOTE_ADDR']) >> 8) - { - return true; - } - } - } - - if(Context::getSessionStatus()) - { - $_SESSION['is_logged'] = false; - } - return false; + function isLogged() + { + return Rhymix\Framework\Session::getMemberSrl() ? true : false; } /** @@ -268,39 +241,7 @@ class memberModel extends member */ function getLoggedInfo() { - // Return session info if session info is requested and the user is logged-in - if($this->isLogged()) - { - $logged_info = Context::get('logged_info'); - // Admin/Group list defined depending on site_module_info - $site_module_info = Context::get('site_module_info'); - if($site_module_info->site_srl) - { - $logged_info->group_list = $this->getMemberGroups($logged_info->member_srl, $site_module_info->site_srl); - // Add is_site_admin bool variable into logged_info if site_administrator is - $oModuleModel = getModel('module'); - if($oModuleModel->isSiteAdmin($logged_info)) $logged_info->is_site_admin = true; - else $logged_info->is_site_admin = false; - } - else - { - // Register a default group if the site doesn't have a member group - if(count($logged_info->group_list) === 0) - { - $default_group = $this->getDefaultGroup(0); - $oMemberController = getController('member'); - $oMemberController->addMemberToGroup($logged_info->member_srl, $default_group->group_srl, 0); - $groups[$default_group->group_srl] = $default_group->title; - $logged_info->group_list = $groups; - } - - $logged_info->is_site_admin = false; - } - Context::set('logged_info', $logged_info); - - return $logged_info; - } - return new stdClass; + return Context::get('logged_info'); } /** @@ -398,6 +339,7 @@ class memberModel extends member } $info->signature = $this->getSignature($info->member_srl); $info->group_list = $this->getMemberGroups($info->member_srl, $site_srl); + $info->is_site_admin = $oModuleModel->isSiteAdmin($info) ? true : false; $extra_vars = unserialize($info->extra_vars); unset($info->extra_vars); @@ -493,8 +435,7 @@ class memberModel extends member */ function getLoggedMemberSrl() { - if(!$this->isLogged()) return; - return $_SESSION['member_srl']; + return Rhymix\Framework\Session::getMemberSrl(); } /** @@ -527,6 +468,12 @@ class memberModel extends member $args->site_srl = $site_srl; $output = executeQueryArray('member.getMemberGroups', $args); $group_list = $output->data; + if (!count($group_list)) + { + $default_group = $this->getDefaultGroup($site_srl); + getController('member')->addMemberToGroup($member_srl, $default_group->group_srl, $site_srl); + $group_list[$default_group->group_srl] = $default_group->title; + } //insert in cache Rhymix\Framework\Cache::set($cache_key, $group_list, 0, true); } diff --git a/modules/member/member.view.php b/modules/member/member.view.php index 147680318..e7a1a5427 100644 --- a/modules/member/member.view.php +++ b/modules/member/member.view.php @@ -430,6 +430,30 @@ class memberView extends member $this->setTemplateFile('saved_list'); } + /** + * @brief Display the login management page + */ + function dispMemberActiveLogins() + { + $logged_info = Context::get('logged_info'); + if (!$logged_info->member_srl) + { + return $this->stop('msg_not_logged'); + } + + $args = new stdClass(); + $args->member_srl = $logged_info->member_srl; + $args->page = (int)Context::get('page'); + $output = executeQueryArray('member.getAutologin', $args); + Context::set('total_count', $output->total_count); + Context::set('total_page', $output->total_page); + Context::set('page', $output->page); + Context::set('active_logins', $output->data); + Context::set('page_navigation', $output->page_navigation); + + $this->setTemplateFile('active_logins'); + } + /** * @brief Display the login form */ diff --git a/modules/member/queries/deleteAutologin.xml b/modules/member/queries/deleteAutologin.xml index 789171804..41aabfeec 100644 --- a/modules/member/queries/deleteAutologin.xml +++ b/modules/member/queries/deleteAutologin.xml @@ -3,7 +3,8 @@
| {$lang->no} | +{$lang->cmd_login_browser_info} | +{$lang->cmd_initial_login} | +{$lang->cmd_recent_visit} | +{$lang->cmd_delete} | +
|---|---|---|---|---|
| {$no} | +
+ {escape($autologin_info->user_agent->browser)} {escape($autologin_info->user_agent->version)} + {escape($autologin_info->user_agent->os)} {$autologin_info->user_agent->is_tablet ? 'Tablet' : ($autologin_info->user_agent->is_mobile ? 'Mobile' : 'PC')} + |
+ {zdate($autologin_info->regdate, 'Y-m-d H:i')} {$autologin_info->ipaddress} |
+ {zdate($autologin_info->last_visit, 'Y-m-d H:i')} {$autologin_info->last_ipaddress} |
+ + |
{$lang->about_password_hashing_auto_upgrade}
+{$lang->about_password_change_invalidate_other_sessions}
+