[TASK] Only set FE user cookie if session data or user logged in 30/27230/8
authorBenjamin Mack <benni@typo3.org>
Sat, 1 Feb 2014 12:48:13 +0000 (13:48 +0100)
committerBenjamin Mack <benni@typo3.org>
Thu, 6 Feb 2014 16:16:04 +0000 (17:16 +0100)
Currently the FE session cookie is set on every request
and since 4.2 the sessionID is generated again on every
request unless the user is logged in. This is implemented
for avoiding the security problem of the
session fixation (see #19831).

If an installation does not use FE session cookies at all,
an option (TYPO3_CONF_VARS->FE->dontSetCookie)
never sets the cookie.

As the current behavior for non-logged-in FE calls
is not usable, the behaviour is changed to only set
the cookie if the user is logged in or the session data
is modified. The last example is helpful for websites
with e.g. a shopping cart on non-logged-in pages.
Currently, if an extension is trying to implement the
latter, the extension needs to hook or XCLASS the
FrontendUserAuthentication class to set the cookie
whenever needed.

Additionally, the security problem still exists if the
cookie is not set by TYPO3 itself, that's why the
cookie can only be set if there is a valid entry in
fe_user_sessions.

if using external caching (e.g. reverse proxies),
a "unneeded" cookie is always set currently,
which extensions like EXT:moc_varnish or
EXT:cachinfo mock to only set the cookie
if needed.

The attached patch removes the default-setting
of a cookie in the frontend, and only triggers
the setcookie() function when sessionData is
added or a user is logged-in.

Resolves: #55549
Releases: 6.2
Change-Id: If478bc00c2c55dda0cc38a898a1288098891671f
Reviewed-on: https://review.typo3.org/27230
Reviewed-by: Markus Klein
Tested-by: Markus Klein
Reviewed-by: Wouter Wolters
Tested-by: Wouter Wolters
Reviewed-by: Benjamin Mack
Tested-by: Benjamin Mack
NEWS.md
typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php
typo3/sysext/frontend/Classes/Authentication/FrontendUserAuthentication.php
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/install/Classes/Service/SilentConfigurationUpgradeService.php

diff --git a/NEWS.md b/NEWS.md
index 70b85b1..a36b97d 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -146,6 +146,18 @@ The extension works with the same options as before.
 Previously $row['cache_data'] was a serialized array. To avoid double serializing and unserializing,
 from now on $row['cache_data'] is just reconstituted as array when fetching from cache.
 
+* Frontend Cookie now only set when needed, not set by default anymore
+
+The cookie "fe_typo_user" set in the frontend by each request, is now only
+being set if the session data is used via $TSFE->fe_user->setKey('ses')
+so it can be used for shopping baskets for non-logged-in users
+out-of-the-box without hacking the default behaviour of setting the
+cookie.
+The previous behaviour always set the "fe_typo_user" cookie, but changed
+the session ID on each request, until it was fixated by a user login.
+The superfluous option "dontSetCookie" is now ineffective as the cookie
+is not set anymore by default.
+
 ### Administration / Customization
 
 * Content-length header (TypoScript setting config.enableContentLengthHeader)
@@ -191,4 +203,4 @@ Pages and content elements are now categorizable by default.
 * New menu types
 
 The "Special Menus" content element type now offers the possibility to display
-a list of categorized pages or content elements.
\ No newline at end of file
+a list of categorized pages or content elements.
index 3ec45b9..b97cc41 100644 (file)
@@ -274,6 +274,7 @@ abstract class AbstractUserAuthentication {
        // to come from $_COOKIE).
        /**
         * @todo Define visibility
+        * @deprecated since TYPO3 CMS 6.2, remove two versions later, use $this->isCookieSet() instead
         */
        public $cookieId;
 
@@ -315,12 +316,19 @@ abstract class AbstractUserAuthentication {
         */
        public $forceSetCookie = FALSE;
 
-       // Will prevent the setting of the session cookie (takes precedence over forceSetCookie)
        /**
+        * Will prevent the setting of the session cookie (takes precedence over forceSetCookie)
+        * @var bool
         * @todo Define visibility
         */
        public $dontSetCookie = FALSE;
 
+       /**
+        * is set to know on this current request if a cookie was set
+        * @var bool
+        */
+       protected $cookieWasSetOnCurrentRequest = FALSE;
+
        // If set, the challenge value will be stored in a session as well so the
        // server can check that is was not forged.
        /**
@@ -390,10 +398,7 @@ abstract class AbstractUserAuthentication {
                // $id is set to ses_id if cookie is present. Else set to FALSE, which will start a new session
                $id = $this->getCookie($this->name);
                $this->svConfig = $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth'];
-               // If we have a flash client, take the ID from the GP
-               if (!$id && $GLOBALS['CLIENT']['BROWSER'] == 'flash') {
-                       $id = GeneralUtility::_GP($this->name);
-               }
+
                // If fallback to get mode....
                if (!$id && $this->getFallBack && $this->get_name) {
                        $id = isset($_GET[$this->get_name]) ? GeneralUtility::_GET($this->get_name) : '';
@@ -402,7 +407,7 @@ abstract class AbstractUserAuthentication {
                        }
                        $mode = 'get';
                }
-               $this->cookieId = $id;
+
                // If new session or client tries to fix session...
                if (!$id || !$this->isExistingSessionRecord($id)) {
                        // New random session-$id is made
@@ -470,6 +475,7 @@ abstract class AbstractUserAuthentication {
         * Sets the session cookie for the current disposal.
         *
         * @return void
+        * @throws \TYPO3\CMS\Core\Exception
         */
        protected function setSessionCookie() {
                $isSetSessionCookie = $this->isSetSessionCookie();
@@ -489,6 +495,7 @@ abstract class AbstractUserAuthentication {
                        // Do not set cookie if cookieSecure is set to "1" (force HTTPS) and no secure channel is used:
                        if ((int)$settings['cookieSecure'] !== 1 || GeneralUtility::getIndpEnv('TYPO3_SSL')) {
                                setcookie($this->name, $this->id, $cookieExpire, $cookiePath, $cookieDomain, $cookieSecure, $cookieHttpOnly);
+                               $this->cookieWasSetOnCurrentRequest = TRUE;
                        } else {
                                throw new \TYPO3\CMS\Core\Exception('Cookie was not set since HTTPS was forced in $TYPO3_CONF_VARS[SYS][cookieSecure].', 1254325546);
                        }
@@ -543,6 +550,7 @@ abstract class AbstractUserAuthentication {
         * @return string The value stored in the cookie
         */
        protected function getCookie($cookieName) {
+               $cookieValue = '';
                if (isset($_SERVER['HTTP_COOKIE'])) {
                        $cookies = GeneralUtility::trimExplode(';', $_SERVER['HTTP_COOKIE']);
                        foreach ($cookies as $cookie) {
@@ -981,6 +989,19 @@ abstract class AbstractUserAuthentication {
        }
 
        /**
+        * Empty / unset the cookie
+        *
+        * @param string $cookieName usually, this is $this->name
+        * @return void
+        */
+       public function removeCookie($cookieName) {
+               $cookieDomain = $this->getCookieDomain();
+               // If no cookie domain is set, use the base path
+               $cookiePath = $cookieDomain ? '/' : GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
+               setcookie($cookieName, NULL, -1, $cookiePath, $cookieDomain);
+       }
+
+       /**
         * Determine whether there's an according session record to a given session_id
         * in the database. Don't care if session record is still valid or not.
         *
@@ -996,6 +1017,17 @@ abstract class AbstractUserAuthentication {
                return $row[0] ? TRUE : FALSE;
        }
 
+       /**
+        * Returns whether this request is going to set a cookie
+        * or a cookie was already found in the system
+        * replaces the old functionality for "$this->cookieId"
+        *
+        * @return boolean Returns TRUE if a cookie is set
+        */
+       public function isCookieSet() {
+               return $this->cookieWasSetOnCurrentRequest || $this->getCookie($this->name);
+       }
+
        /*************************
         *
         * SQL Functions
index dea48e1..a9b829d 100644 (file)
@@ -686,7 +686,6 @@ return array(
                'defaultTypoScript_constants.' => array(),              // Lines of TS to include after a static template with the uid = the index in the array (Constants)
                'defaultTypoScript_setup' => '',                // String (textarea). Enter lines of default TypoScript, setup-field.
                'defaultTypoScript_setup.' => array(),          // Lines of TS to include after a static template with the uid = the index in the array (Setup)
-               'dontSetCookie' => FALSE,               // Boolean: If set, the no cookies is attempted to be set in the front end. Of course no userlogins are possible either...
                'additionalAbsRefPrefixDirectories' => '',              // Enter additional directories to be prepended with absRefPrefix. Directories must be comma-separated. TYPO3 already prepends the following directories: media/, typo3conf/ext/, fileadmin/
                'IPmaskMountGroups' => array( // This allows you to specify an array of IPmaskLists/fe_group-uids. If the REMOTE_ADDR of the user matches an IPmaskList, then the given fe_group is add to the gr_list. So this is an automatic mounting of a user-group. But no fe_user is logged in though! This feature is implemented for the default frontend user authentication and might not be implemented for alternative authentication services.
                        // array('IPmaskList_1','fe_group uid'), array('IPmaskList_2','fe_group uid')
index 17ef889..d39a72c 100644 (file)
@@ -180,7 +180,7 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
                }
                // Process the redirect
                if (($this->logintype === 'login' || $this->logintype === 'logout') && $this->redirectUrl && !$this->noRedirect) {
-                       if (!$GLOBALS['TSFE']->fe_user->cookieId) {
+                       if (!$GLOBALS['TSFE']->fe_user->isCookieSet()) {
                                $content .= $this->cObj->stdWrap($this->pi_getLL('cookie_warning', '', TRUE), $this->conf['cookieWarning_stdWrap.']);
                        } else {
                                // Add hook for extra processing before redirect
index 8fa3b25..9561435 100644 (file)
@@ -113,6 +113,10 @@ class FrontendUserAuthentication extends \TYPO3\CMS\Core\Authentication\Abstract
         * Default constructor.
         */
        public function __construct() {
+               // Disable cookie by default, will be activated if saveSessionData() is called,
+               // a user is logging-in or an existing session is found
+               $this->dontSetCookie = TRUE;
+
                $this->session_table = 'fe_sessions';
                $this->name = self::getCookieName();
                $this->get_name = 'ftu';
@@ -240,6 +244,19 @@ class FrontendUserAuthentication extends \TYPO3\CMS\Core\Authentication\Abstract
        }
 
        /**
+        * Creates a user session record and returns its values.
+        * However, as the FE user cookie is normally not set, this has to be done
+        * before the parent class is doing the rest.
+        *
+        * @param array $tempuser User data array
+        * @return array The session data for the newly created session.
+        */
+       public function createUserSession($tempuser) {
+               $this->setSessionCookie();
+               return parent::createUserSession($tempuser);
+       }
+
+       /**
         * Will select all fe_groups records that the current fe_user is member of - and which groups are also allowed in the current domain.
         * It also accumulates the TSconfig for the fe_user/fe_groups in ->TSdataArray
         *
@@ -396,6 +413,10 @@ class FrontendUserAuthentication extends \TYPO3\CMS\Core\Authentication\Abstract
                        if (empty($this->sesData)) {
                                // Remove session-data
                                $this->removeSessionData();
+                               // Remove cookie if not logged in as the session data is removed as well
+                               if (!empty($this->user['uid'])) {
+                                       $this->removeCookie($this->name);
+                               }
                        } elseif ($this->sessionDataTimestamp === NULL) {
                                // Write new session-data
                                $insertFields = array(
@@ -405,6 +426,8 @@ class FrontendUserAuthentication extends \TYPO3\CMS\Core\Authentication\Abstract
                                );
                                $this->sessionDataTimestamp = $GLOBALS['EXEC_TIME'];
                                $GLOBALS['TYPO3_DB']->exec_INSERTquery('fe_session_data', $insertFields);
+                               // Now set the cookie (= fix the session)
+                               $this->setSessionCookie();
                        } else {
                                // Update session data
                                $updateFields = array(
@@ -427,6 +450,20 @@ class FrontendUserAuthentication extends \TYPO3\CMS\Core\Authentication\Abstract
        }
 
        /**
+        * Log out current user!
+        * Removes the current session record, sets the internal ->user array to a blank string
+        * Thereby the current user (if any) is effectively logged out!
+        * Additionally the cookie is removed
+        *
+        * @return void
+        */
+       public function logoff() {
+               parent::logoff();
+               // Remove the cookie on log-off
+               $this->removeCookie($this->name);
+       }
+
+       /**
         * Executes the garbage collection of session data and session.
         * The lifetime of session data is defined by $TYPO3_CONF_VARS['FE']['sessionDataLifetime'].
         *
@@ -532,10 +569,10 @@ class FrontendUserAuthentication extends \TYPO3\CMS\Core\Authentication\Abstract
         * @todo Define visibility
         */
        public function record_registration($recs, $maxSizeOfSessionData = 0) {
-               // Storing value ONLY if there is a confirmed cookie set (->cookieID),
+               // Storing value ONLY if there is a confirmed cookie set,
                // otherwise a shellscript could easily be spamming the fe_sessions table
                // with bogus content and thus bloat the database
-               if (!$maxSizeOfSessionData || $this->cookieId) {
+               if (!$maxSizeOfSessionData || $this->isCookieSet()) {
                        if ($recs['clear_all']) {
                                $this->setKey('ses', 'recs', array());
                        }
index 2ce0117..6b6d3c6 100644 (file)
@@ -929,9 +929,6 @@ class TypoScriptFrontendController {
                                unset($cookieName);
                        }
                }
-               if ($this->TYPO3_CONF_VARS['FE']['dontSetCookie']) {
-                       $this->fe_user->dontSetCookie = 1;
-               }
                $this->fe_user->start();
                $this->fe_user->unpack_uc('');
                // Gets session data
index f62e398..1c34c57 100644 (file)
@@ -86,6 +86,8 @@ class SilentConfigurationUpgradeService {
                'FE/simulateStaticDocuments',
                // #52786
                'FE/logfile_dir',
+               // #55549
+               'FE/dontSetCookie',
                // #52011
                'GFX/im_combine_filename',
                // #52088