diff --git a/administrator/components/com_admin/sql/updates/mysql/3.2.3-2014-02-20.sql b/administrator/components/com_admin/sql/updates/mysql/3.2.3-2014-02-20.sql new file mode 100644 index 0000000000000..0bd96df127480 --- /dev/null +++ b/administrator/components/com_admin/sql/updates/mysql/3.2.3-2014-02-20.sql @@ -0,0 +1 @@ +UPDATE `#__extensions` ext1, `#__extensions` ext2 SET ext1.`params` = ext2.`params` WHERE ext1.`name` = 'plg_authentication_cookie' AND ext2.`name` = 'plg_system_remember'; diff --git a/administrator/components/com_admin/sql/updates/postgresql/3.2.3-2014-02-20.sql b/administrator/components/com_admin/sql/updates/postgresql/3.2.3-2014-02-20.sql new file mode 100644 index 0000000000000..9f69a3f91e358 --- /dev/null +++ b/administrator/components/com_admin/sql/updates/postgresql/3.2.3-2014-02-20.sql @@ -0,0 +1 @@ +UPDATE "#__extensions" SET "params" = (SELECT "params" FROM "#__extensions" WHERE "name" = 'plg_system_remember') WHERE "name" = 'plg_authentication_cookie'; diff --git a/administrator/components/com_admin/sql/updates/sqlazure/3.2.3-2014-02-20.sql b/administrator/components/com_admin/sql/updates/sqlazure/3.2.3-2014-02-20.sql new file mode 100644 index 0000000000000..0c4ac188da3c8 --- /dev/null +++ b/administrator/components/com_admin/sql/updates/sqlazure/3.2.3-2014-02-20.sql @@ -0,0 +1 @@ +UPDATE [#__extensions] SET [params] = (SELECT [params] FROM [#__extensions] WHERE [name] = 'plg_system_remember') WHERE [name] = 'plg_authentication_cookie'; \ No newline at end of file diff --git a/administrator/language/en-GB/en-GB.plg_authentication_cookie.ini b/administrator/language/en-GB/en-GB.plg_authentication_cookie.ini index 3ae23c618dae6..6300798211792 100644 --- a/administrator/language/en-GB/en-GB.plg_authentication_cookie.ini +++ b/administrator/language/en-GB/en-GB.plg_authentication_cookie.ini @@ -5,3 +5,9 @@ PLG_AUTH_COOKIE_XML_DESCRIPTION="Handles Joomla's cookie User authentication
Warning! You must have at least one other authentication plugin enabled.
You will also need a plugin such as the System - Remember Me plugin to implement cookie login." PLG_AUTHENTICATION_COOKIE="Authentication - Cookie" +PLG_AUTH_COOKIE_FIELD_COOKIE_LIFETIME_DESC="The number of days until the authentication cookie will expire. Other factors may cause it to expire before this. Longer lengths are less secure." +PLG_AUTH_COOKIE_FIELD_COOKIE_LIFETIME_LABEL="Cookie Lifetime" +PLG_AUTH_COOKIE_ERROR_LOG_INVALIDATED_COOKIES="The authentication tokens were invalidated for user %u because there was no matching record " +PLG_AUTH_COOKIE_ERROR_LOG_LOGIN_FAILED="Cookie login failed for user %u" +PLG_AUTH_COOKIE_FIELD_KEY_LENGTH_DESC="The length of the key to use to encrypt the cookie. Longer lengths are more secure, but they will slow performance." +PLG_AUTH_COOKIE_FIELD_KEY_LENGTH_LABEL="Key Length" diff --git a/administrator/language/en-GB/en-GB.plg_system_remember.ini b/administrator/language/en-GB/en-GB.plg_system_remember.ini index ff6313b5665f5..c0fff5623906d 100644 --- a/administrator/language/en-GB/en-GB.plg_system_remember.ini +++ b/administrator/language/en-GB/en-GB.plg_system_remember.ini @@ -5,9 +5,3 @@ PLG_REMEMBER_XML_DESCRIPTION="Provides remember me functionality. The cookie authentication plugin must be enabled for this plugin to function." PLG_SYSTEM_REMEMBER="System - Remember Me" -PLG_SYSTEM_REMEMBER_FIELD_COOKIE_LIFETIME_DESC="The number of days until the Remember Me cookie will expire. Other factors may cause it to expire before this. Longer lengths are less secure." -PLG_SYSTEM_REMEMBER_FIELD_COOKIE_LIFETIME_LABEL="Cookie Lifetime" -PLG_SYSTEM_REMEMBER_ERROR_LOG_INVALIDATED_COOKIES="The remember me tokens were invalidated for user %u because there was no matching record " -PLG_SYSTEM_REMEMBER_ERROR_LOG_LOGIN_FAILED="Remember me login failed for user %u" -PLG_SYSTEM_REMEMBER_FIELD_KEY_LENGTH_DESC="The length of the key to use to encrypt the cookie. Longer lengths are more secure, but they will slow performance." -PLG_SYSTEM_REMEMBER_FIELD_KEY_LENGTH_LABEL="Key Length" diff --git a/libraries/cms/application/cms.php b/libraries/cms/application/cms.php index bb1a4d7a14378..ee06eb6fc3d4b 100644 --- a/libraries/cms/application/cms.php +++ b/libraries/cms/application/cms.php @@ -800,13 +800,6 @@ public function login($credentials, $options = array()) $options['user'] = $user; $options['responseType'] = $response->type; - if (isset($response->length) && isset($response->secure) && isset($response->lifetime)) - { - $options['length'] = $response->length; - $options['secure'] = $response->secure; - $options['lifetime'] = $response->lifetime; - } - // The user is successfully logged in. Run the after login events $this->triggerEvent('onUserAfterLogin', array($options)); } diff --git a/libraries/joomla/user/helper.php b/libraries/joomla/user/helper.php index f7b38f7e84611..41316ef36f6d9 100644 --- a/libraries/joomla/user/helper.php +++ b/libraries/joomla/user/helper.php @@ -718,7 +718,7 @@ private static function _bin($hex) * @return boolean True on success * * @since 3.2 - * @see JInput::setCookie for more details + * @deprecated 4.0 This is handled in the authentication plugin itself. The 'invalid' column in the db should be removed as well */ public static function invalidateCookie($userId, $cookieName) { @@ -746,6 +746,7 @@ public static function invalidateCookie($userId, $cookieName) * @return mixed Database query result * * @since 3.2 + * @deprecated 4.0 This is handled in the authentication plugin itself */ public static function clearExpiredTokens() { @@ -765,6 +766,7 @@ public static function clearExpiredTokens() * @return mixed An array of information from an authentication cookie or false if there is no cookie * * @since 3.2 + * @deprecated 4.0 This is handled in the authentication plugin itself */ public static function getRememberCookieData() { diff --git a/plugins/authentication/cookie/cookie.php b/plugins/authentication/cookie/cookie.php index 5dcf9cf0aceb8..f484e7df1b859 100644 --- a/plugins/authentication/cookie/cookie.php +++ b/plugins/authentication/cookie/cookie.php @@ -15,6 +15,8 @@ * @package Joomla.Plugin * @subpackage Authentication.cookie * @since 3.2 + * @note Code based on http://jaspan.com/improved_persistent_login_cookie_best_practice + * and http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice/ */ class PlgAuthenticationCookie extends JPlugin { @@ -53,35 +55,53 @@ public function onUserAuthenticate($credentials, $options, &$response) return false; } - JLoader::register('JAuthentication', JPATH_LIBRARIES . '/joomla/user/authentication.php'); - $response->type = 'Cookie'; - // We need to validate the cookie data because there may be no Remember Me plugin to do it. - // Create the cookie name and data. - $rememberArray = JUserHelper::getRememberCookieData(); + // Get cookie + $cookieName = JUserHelper::getShortHashedUserAgent(); + + $cookieValue = $this->app->input->cookie->get($cookieName); + if (!$cookieValue) + { + return; + } + + $cookieArray = explode('.', $cookieValue); - if ($rememberArray == false) + // Check for valid cookie value + if (count($cookieArray) != 2) { + // Destroy the cookie in the browser. + $this->app->input->cookie->set($cookieName, false, time() - 42000, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain')); + JLog::add('Invalid cookie detected.', JLog::WARNING, 'error'); + return false; } - list($privateKey, $series, $uastring) = $rememberArray; + // Filter series since we're going to use it in the query + $filter = new JFilterInput; + $series = $filter->clean($cookieArray[1], 'ALNUM'); + + // Remove expired tokens + $query = $this->db->getQuery(true) + ->delete('#__user_keys') + ->where($this->db->quoteName('time') . ' < ' . $this->db->quote(time())); + $this->db->setQuery($query)->execute(); // Find the matching record if it exists. $query = $this->db->getQuery(true) - ->select($this->db->quoteName(array('user_id', 'token', 'series', 'time', 'invalid'))) - ->from($this->db->quoteName('#__user_keys')) - ->where($this->db->quoteName('series') . ' = ' . $this->db->quote(base64_encode($series))) - ->where($this->db->quoteName('uastring') . ' = ' . $this->db->quote($uastring)) - ->order($this->db->quoteName('time') . ' DESC'); + ->select($this->db->quoteName(array('user_id', 'token', 'series', 'time'))) + ->from($this->db->quoteName('#__user_keys')) + ->where($this->db->quoteName('series') . ' = ' . $this->db->quote($series)) + ->where($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName)) + ->order($this->db->quoteName('time') . ' DESC'); $results = $this->db->setQuery($query)->loadObjectList(); - $countResults = count($results); - - if ($countResults !== 1) + if (count($results) !== 1) { + // Destroy the cookie in the browser. + $this->app->input->cookie->set($cookieName, false, time() - 42000, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain')); $response->status = JAuthentication::STATUS_FAILURE; return; @@ -90,44 +110,34 @@ public function onUserAuthenticate($credentials, $options, &$response) // We have a user with one cookie with a valid series and a corresponding record in the database. else { - if (substr($results[0]->token, 0, 4) === '$2y$') - { - if (JCrypt::hasStrongPasswordSupport()) - { - $match = password_verify($privateKey, $results[0]->token); - } - } - else + $token = JUserHelper::hashPassword($cookieArray[0]); + if (!JUserHelper::verifyPassword($cookieArray[0], $results[0]->token)) { - if (JCrypt::timingSafeCompare($results[0]->token, $privateKey)) - { - $match = true; - } - } + // This is a real attack! Either the series was guessed correctly or a cookie was stolen and used twice (once by attacker and once by victim). + // Delete all tokens for this user! + $query = $this->db->getQuery(true) + ->delete('#__user_keys') + ->where($this->db->quoteName('user_id') . ' = ' . $this->db->quote($results[0]->user_id)); + $this->db->setQuery($query)->execute(); + + // Destroy the cookie in the browser. + $this->app->input->cookie->set($cookieName, false, time() - 42000, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain')); + + // Issue warning by email to user and/or admin? + + JLog::add(JText::sprintf('PLG_AUTH_COOKIE_ERROR_LOG_LOGIN_FAILED', $results[0]->user_id), JLog::WARNING, 'security'); - if (empty($match)) - { - JUserHelper::invalidateCookie($results[0]->user_id, $uastring); - JLog::add(JText::sprintf('PLG_SYSTEM_REMEMBER_ERROR_LOG_LOGIN_FAILED', $user->username), JLog::WARNING, 'security'); $response->status = JAuthentication::STATUS_FAILURE; return false; } } - // Set cookie params. - if (!empty($options['lifetime']) && !empty($options['length']) && !empty($options['secure'])) - { - $response->lifetime = $options['lifetime']; - $response->length = $options['length']; - $response->secure = $options['secure']; - } - // Make sure there really is a user with this name and get the data for the session. $query = $this->db->getQuery(true) ->select($this->db->quoteName(array('id', 'username', 'password'))) ->from($this->db->quoteName('#__users')) - ->where($this->db->quoteName('username') . ' = ' . $this->db->quote($credentials['username'])); + ->where($this->db->quoteName('username') . ' = ' . $this->db->quote($results[0]->user_id)); $result = $this->db->setQuery($query)->loadObject(); @@ -135,13 +145,6 @@ public function onUserAuthenticate($credentials, $options, &$response) { // Bring this in line with the rest of the system $user = JUser::getInstance($result->id); - $cookieName = JUserHelper::getShortHashedUserAgent(); - - // If there is no cookie, bail out - if (!$this->app->input->cookie->get($cookieName)) - { - return; - } // Set response data. $response->username = $result->username; @@ -160,4 +163,154 @@ public function onUserAuthenticate($credentials, $options, &$response) $response->error_message = JText::_('JGLOBAL_AUTH_NO_USER'); } } + + /** + * We set the authentication cookie only after login is successfullly finished. + * We set a new cookie either for a user with no cookies or one + * where the user used a cookie to authenticate. + * + * @param array options Array holding options + * + * @return boolean True on success + * + * @since 3.2 + */ + public function onUserAfterLogin($options) + { + // No remember me for admin + if ($this->app->isAdmin()) + { + return false; + } + + if (isset($options['responseType']) && $options['responseType'] == 'Cookie') + { + // Logged in using a cookie + $cookieName = JUserHelper::getShortHashedUserAgent(); + + // We need the old data to get the existing series + $cookieValue = $this->app->input->cookie->get($cookieName); + $cookieArray = explode('.', $cookieValue); + // Filter series since we're going to use it in the query + $filter = new JFilterInput; + $series = $filter->clean($cookieArray[1], 'ALNUM'); + } + elseif (!empty($options['remember'])) + { + // Remember checkbox is set + $cookieName = JUserHelper::getShortHashedUserAgent(); + + // Create an unique series which will be used over the lifespan of the cookie + $unique = false; + do + { + $series = JUserHelper::genRandomPassword(20); + + $query = $this->db->getQuery(true) + ->select($this->db->quoteName('series')) + ->from($this->db->quoteName('#__user_keys')) + ->where($this->db->quoteName('series') . ' = ' . $this->db->quote($series)); + + $results = $this->db->setQuery($query)->loadResult(); + + if (is_null($results)) + { + $unique = true; + } + } + while ($unique === false); + } + else + { + return false; + } + + // Get the parameter values + $lifetime = $this->params->get('cookie_lifetime', '60') * 24 * 60 * 60; + $length = $this->params->get('key_length', '16'); + + // Generate new cookie + $token = JUserHelper::genRandomPassword($length); + $cookieValue = $token . '.' . $series; + + // Overwrite existing cookie with new value + $this->app->input->cookie->set( + $cookieName, $cookieValue, time() + $lifetime, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain'), $this->app->isSSLConnection() + ); + + $query = $this->db->getQuery(true); + + if (!empty($options['remember'])) + { + // Create new record + $query + ->insert($this->db->quoteName('#__user_keys')) + ->set($this->db->quoteName('user_id') . ' = ' . $this->db->quote($options['user']->username)) + ->set($this->db->quoteName('series') . ' = ' . $this->db->quote($series)) + ->set($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName)) + ->set($this->db->quoteName('time') . ' = ' . (time() + $lifetime)); + } + else + { + // Update existing record with new token + $query + ->update($this->db->quoteName('#__user_keys')) + ->where($this->db->quoteName('user_id') . ' = ' . $this->db->quote($options['user']->username)) + ->where($this->db->quoteName('series') . ' = ' . $this->db->quote($series)) + ->where($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName)); + } + + $hashed_token = JUserHelper::hashPassword($token); + $query + ->set($this->db->quoteName('token') . ' = ' . $this->db->quote($hashed_token)); + + $this->db->setQuery($query)->execute(); + + return true; + } + + /** + * This is where we delete any authentication cookie when a user logs out + * + * @param array $options Array holding options (length, timeToExpiration) + * + * @return boolean True on success + * + * @since 3.2 + */ + public function onUserAfterLogout($options) + { + // No remember me for admin + if ($this->app->isAdmin()) + { + return false; + } + + $cookieName = JUserHelper::getShortHashedUserAgent(); + $cookieValue = $this->app->input->cookie->get($cookieName); + // There are no cookies to delete. + if (!$cookieValue) + { + return true; + } + + $cookieArray = explode('.', $cookieValue); + + // Filter series since we're going to use it in the query + $filter = new JFilterInput; + $series = $filter->clean($cookieArray[1], 'ALNUM'); + + // Remove the record from the database + $query = $this->db->getQuery(true); + $query + ->delete('#__user_keys') + ->where($this->db->quoteName('series') . ' = ' . $this->db->quote($series)); + + $this->db->setQuery($query)->execute(); + + // Destroy the cookie + $this->app->input->cookie->set($cookieName, false, time() - 42000, $this->app->get('cookie_path', '/'), $this->app->get('cookie_domain')); + + return true; + } } diff --git a/plugins/authentication/cookie/cookie.xml b/plugins/authentication/cookie/cookie.xml index e72b7f757f02f..42746552ac944 100644 --- a/plugins/authentication/cookie/cookie.xml +++ b/plugins/authentication/cookie/cookie.xml @@ -17,4 +17,30 @@ en-GB.plg_authentication_cookie.ini en-GB.plg_authentication_cookie.sys.ini + + +
+ + + + + + + + + +
+
+
diff --git a/plugins/system/remember/remember.php b/plugins/system/remember/remember.php index 6aed3d02583e5..ea01fcf43dd6a 100644 --- a/plugins/system/remember/remember.php +++ b/plugins/system/remember/remember.php @@ -15,9 +15,8 @@ * @package Joomla.Plugin * @subpackage System.remember * @since 1.5 - * @note Code improvements inspired by http://jaspan.com/improved_persistent_login_cookie_best_practice - * and http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice/ */ + class PlgSystemRemember extends JPlugin { /** @@ -29,79 +28,8 @@ class PlgSystemRemember extends JPlugin protected $app; /** - * Database object. - * - * @var JDatabaseDriver - * @since 3.2 - */ - protected $db; - - /** - * Domain for the cookie. - * - * @var string - * @since 3.2 - */ - protected $cookie_domain; - - /** - * Path for the cookie. - * - * @var string - * @since 3.2 - */ - protected $cookie_path; - - /** - * Whether to set as secure or not. - * - * @var boolean - * @since 3.2 - */ - protected $secure = false; - - /** - * Cookie lifetime in days. - * - * @var integer - * @since 3.2 - */ - protected $lifetime; - - /** - * Length of random string. - * - * @var integer - * @since 3.2 - */ - protected $length; - - /** - * Constructor. - * - * Used to set the application and database properties. - * - * @param object &$subject The object to observe. - * @param array $config An optional associative array of configuration settings. - * Recognized key values include 'name', 'group', 'params', 'language' - * (this list is not meant to be comprehensive). - * - * @since 3.2 - */ - public function __construct(&$subject, $config = array()) - { - parent::__construct($subject, $config); - - // Use domain and path set in config for cookie if it exists. - $this->cookie_domain = $this->app->get('cookie_domain', ''); - $this->cookie_path = $this->app->get('cookie_path', '/'); - $this->lifetime = time() + ($this->params->get('cookie_lifetime', '60') * 24 * 60 * 60); - $this->secure = $this->app->isSSLConnection(); - $this->length = $this->params->get('key_length', '16'); - } - - /** - * Remember me method to run onAfterInitialise. + * Remember me method to run onAfterInitialise + * Only purpose is to initialise the login authentication process if a cookie is present * * @return boolean * @@ -116,79 +44,36 @@ public function onAfterInitialise() return false; } - $user = JFactory::getUser(); - - $this->app->rememberCookieLifetime = $this->lifetime; - $this->app->rememberCookieSecure = $this->secure; - $this->app->rememberCookieLength = $this->length; - - // Check for a cookie. - if ($user->get('guest') == 1) + // Check for a cookie if user is not logged in + if (JFactory::getUser()->get('guest')) { - // Create the cookie name and data. - $rememberArray = JUserHelper::getRememberCookieData(); + $cookieName = JUserHelper::getShortHashedUserAgent(); - if ($rememberArray !== false) + // Check for the cookie + if ($this->app->input->cookie->get($cookieName)) { - if (count($rememberArray) != 3) - { - // Destroy the cookie in the browser. - $this->app->input->cookie->set(end($rememberArray), false, time() - 42000, $this->app->get('cookie_path'), $this->app->get('cookie_domain')); - JLog::add('Invalid cookie detected.', JLog::WARNING, 'error'); - - return false; - } - - list($privateKey, $series, $uastring) = $rememberArray; - - if (!JUserHelper::clearExpiredTokens($this)) - { - JLog::add('Error in deleting expired cookie tokens.', JLog::WARNING, 'error'); - } - - // Find the matching record if it exists. - $query = $this->db->getQuery(true) - ->select($this->db->quoteName(array('user_id', 'token', 'series', 'time', 'invalid'))) - ->from($this->db->quoteName('#__user_keys')) - ->where($this->db->quoteName('series') . ' = ' . $this->db->quote(base64_encode($series))) - ->where($this->db->quoteName('uastring') . ' = ' . $this->db->quote($uastring)) - ->order($this->db->quoteName('time') . ' DESC'); - - $results = $this->db->setQuery($query)->loadObjectList(); - - $countResults = count($results); - - // We have a user but a cookie that is not in the database, or it is invalid. This is a possible attack, so invalidate everything. - if (($countResults === 0 || $results[0]->invalid != 0) && !empty($results[0]->user_id)) - { - JUserHelper::invalidateCookie($results[0]->user_id, $uastring); - JLog::add(JText::sprintf('PLG_SYSTEM_REMEMBER_ERROR_LOG_INVALIDATED_COOKIES', $user->username), JLog::WARNING, 'security'); - - // Possibly e-mail user and admin here. - return false; - } + return $this->app->login(array('username' => ''), array('silent' => true)); + } + } - // We have a user with one cookie with a valid series and a corresponding record in the database. - if ($countResults === 1) - { - if (!JCrypt::timingSafeCompare($results[0]->token, $privateKey)) - { - JUserHelper::invalidateCookie($results[0]->user_id, $uastring); - JLog::add(JText::sprintf('PLG_SYSTEM_REMEMBER_ERROR_LOG_LOGIN_FAILED', $user->username), JLog::WARNING, 'security'); + return false; + } - return false; - } + public function onUserLogout($options) + { + // No remember me for admin + if ($this->app->isAdmin()) + { + return false; + } - // Set up the credentials array to pass to onUserAuthenticate. - $credentials = array( - 'username' => $results[0]->user_id, - ); + $cookieName = JUserHelper::getShortHashedUserAgent(); - return $this->app->login($credentials, array('silent' => true, 'lifetime' => $this->lifetime, 'secure' => $this->secure, 'length' => $this->length)); - } - } + // Check for the cookie + if ($this->app->input->cookie->get($cookieName)) + { + // Make sure authentication group is loaded to process onUserAfterLogout event + JPluginHelper::importPlugin('authentication'); } - - return false; } } diff --git a/plugins/system/remember/remember.xml b/plugins/system/remember/remember.xml index 6e7e3d14df98f..10b1310f7b178 100644 --- a/plugins/system/remember/remember.xml +++ b/plugins/system/remember/remember.xml @@ -17,31 +17,4 @@ en-GB.plg_system_remember.ini en-GB.plg_system_remember.sys.ini - - -
- - - - - - - - - -
-
-
- diff --git a/plugins/user/joomla/joomla.php b/plugins/user/joomla/joomla.php index 6fcbe51a44d62..da600a4964bdf 100644 --- a/plugins/user/joomla/joomla.php +++ b/plugins/user/joomla/joomla.php @@ -321,154 +321,4 @@ protected function _getUser($user, $options = array()) return $instance; } - - /** - * We set the authentication cookie only after login is successfullly finished. - * We set a new cookie either for a user with no cookies or one - * where the user used a cookie to authenticate. - * - * @param array $options Array holding options - * - * @return boolean True on success - * - * @since 3.2 - */ - public function onUserAfterLogin($options) - { - // Currently this portion of the method only applies to Cookie based login. - if (!isset($options['responseType']) || ($options['responseType'] != 'Cookie' && empty($options['remember']))) - { - return true; - } - - // We get the parameter values differently for cookie and non-cookie logins. - $cookieLifetime = empty($options['lifetime']) ? $this->app->rememberCookieLifetime : $options['lifetime']; - $length = empty($options['length']) ? $this->app->rememberCookieLength : $options['length']; - $secure = empty($options['secure']) ? $this->app->rememberCookieSecure : $options['secure']; - - // We need the old data to match against the current database - $rememberArray = JUserHelper::getRememberCookieData(); - - $privateKey = JUserHelper::genRandomPassword($length); - - // We are going to concatenate with . so we need to remove it from the strings. - $privateKey = str_replace('.', '', $privateKey); - - $cryptedKey = JUserHelper::getCryptedPassword($privateKey, '', 'bcrypt', false); - - $cookieName = JUserHelper::getShortHashedUserAgent(); - - // Create an identifier and make sure that it is unique. - $unique = false; - - do - { - // Unique identifier for the device-user - $series = JUserHelper::genRandomPassword(20); - - // We are going to concatenate with . so we need to remove it from the strings. - $series = str_replace('.', '', $series); - - $query = $this->db->getQuery(true) - ->select($this->db->quoteName('series')) - ->from($this->db->quoteName('#__user_keys')) - ->where($this->db->quoteName('series') . ' = ' . $this->db->quote(base64_encode($series))); - - $results = $this->db->setQuery($query)->loadResult(); - - if (is_null($results)) - { - $unique = true; - } - } - - while ($unique === false); - - // If a user logs in with non cookie login and remember me checked we will - // delete any invalid entries so that he or she can use remember once again. - if ($options['responseType'] !== 'Cookie') - { - $query = $this->db->getQuery(true) - ->delete('#__user_keys') - ->where($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName)) - ->where($this->db->quoteName('user_id') . ' = ' . $this->db->quote($options['user']->username)); - - $this->db->setQuery($query)->execute(); - } - - $cookieValue = $cryptedKey . '.' . $series . '.' . $cookieName; - - // Destroy the old cookie. - $this->app->input->cookie->set($cookieName, false, time() - 42000, $this->app->get('cookie_path'), $this->app->get('cookie_domain')); - - // And make a new one. - $this->app->input->cookie->set( - $cookieName, $cookieValue, $cookieLifetime, $this->app->get('cookie_path'), $this->app->get('cookie_domain'), $secure - ); - - $query = $this->db->getQuery(true); - - if (empty($options['user']->cookieLogin) || $options['responseType'] != 'Cookie') - { - // For users doing login from Joomla or other systems - $query->insert($this->db->quoteName('#__user_keys')); - } - else - { - $query - ->update($this->db->quoteName('#__user_keys')) - ->where($this->db->quoteName('user_id') . ' = ' . $this->db->quote($options['user']->username)) - ->where($this->db->quoteName('series') . ' = ' . $this->db->quote(base64_encode($rememberArray[1]))) - ->where($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName)); - } - - $query - ->set($this->db->quoteName('user_id') . ' = ' . $this->db->quote($options['user']->username)) - ->set($this->db->quoteName('time') . ' = ' . $cookieLifetime) - ->set($this->db->quoteName('token') . ' = ' . $this->db->quote($cryptedKey)) - ->set($this->db->quoteName('series') . ' = ' . $this->db->quote(base64_encode($series))) - ->set($this->db->quoteName('invalid') . ' = 0') - ->set($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName)); - - $this->db->setQuery($query)->execute(); - - return true; - } - - /** - * This is where we delete any authentication cookie when a user logs out - * - * @param array $options Array holding options (length, timeToExpiration) - * - * @return boolean True on success - * - * @since 3.2 - */ - public function onUserAfterLogout($options) - { - $rememberArray = JUserHelper::getRememberCookieData(); - - // There are no cookies to delete. - if ($rememberArray === false) - { - return true; - } - - list($privateKey, $series, $cookieName) = $rememberArray; - - // Remove the record from the database - $query = $this->db->getQuery(true); - - $query - ->delete('#__user_keys') - ->where($this->db->quoteName('uastring') . ' = ' . $this->db->quote($cookieName)) - ->where($this->db->quoteName('user_id') . ' = ' . $this->db->quote($options['username'])); - - $this->db->setQuery($query)->execute(); - - // Destroy the cookie - $this->app->input->cookie->set($cookieName, false, time() - 42000, $this->app->get('cookie_path'), $this->app->get('cookie_domain')); - - return true; - } }