[TASK] Improve usability with multiple tabs open 54/27954/7
authorHelmut Hummel <helmut.hummel@typo3.org>
Fri, 28 Feb 2014 18:28:51 +0000 (19:28 +0100)
committerHelmut Hummel <helmut.hummel@typo3.org>
Tue, 4 Mar 2014 18:08:12 +0000 (19:08 +0100)
When the backend user session expires, currently
a popup window is shown which asks the user to
relogin when salted passwords or rsaauth are used
(which is currently our default).

However when a user works with multiple browser tabs
open, it is easy to overlook this popup. When realizing
that the session is expired and the user logs
into the backend again in one tab, the session
is authenticated in all other open tabs, but a
new CSRF protection token has been generated, which
makes working in this tab impossible, especially
because the tokens are now checked for virtually
any action.

This changes cleans up the AjaxLogin functionality
by making use of the new Ajax API introduced lately
and functionality is added so that AjaxLogin also
works with rsaauth and saltedpasswords enabled.

Additionally the form protection framework is slightly
reworked to better support the re-login and token
restore functionality in the AjaxLogin.

The "showRefreshLoginPopup" functionality is still
kept, because AjaxLogin can still not handle
OpenID logins.

Resolves: #56453
Releases: 6.2
Change-Id: Ic6c3415f292d346293c7d2c775288f4ba62ebc15
Reviewed-on: https://review.typo3.org/27954
Reviewed-by: Nicole Cordes
Tested-by: Nicole Cordes
Reviewed-by: Anja Leichsenring
Tested-by: Anja Leichsenring
Reviewed-by: Frans Saris
Tested-by: Frans Saris
Reviewed-by: Helmut Hummel
Tested-by: Helmut Hummel
14 files changed:
typo3/ajax.php
typo3/sysext/backend/Classes/AjaxLoginHandler.php
typo3/sysext/backend/Classes/Controller/BackendController.php
typo3/sysext/backend/Classes/Utility/BackendUtility.php
typo3/sysext/backend/Resources/Public/JavaScript/loginrefresh.js
typo3/sysext/core/Classes/FormProtection/AbstractFormProtection.php
typo3/sysext/core/Classes/FormProtection/BackendFormProtection.php
typo3/sysext/core/Classes/FormProtection/DisabledFormProtection.php
typo3/sysext/core/Tests/Unit/FormProtection/AbstractFormProtectionTest.php
typo3/sysext/core/Tests/Unit/FormProtection/Fixtures/FormProtectionTesting.php
typo3/sysext/rsaauth/Classes/Backend/AjaxLoginHandler.php [new file with mode: 0644]
typo3/sysext/rsaauth/Classes/Hook/BackendHookForAjaxLogin.php [new file with mode: 0644]
typo3/sysext/rsaauth/ext_localconf.php
typo3/sysext/saltedpasswords/ext_localconf.php

index 7ea5112..b6dc9df 100644 (file)
@@ -39,7 +39,8 @@ $noUserAjaxIDs = array(
        'BackendLogin::logout',
        'BackendLogin::refreshLogin',
        'BackendLogin::isTimedOut',
-       'BackendLogin::getChallenge'
+       'BackendLogin::getChallenge',
+       'BackendLogin::getRsaPublicKey',
 );
 
 // First get the ajaxID
index 4deec34..6710f79 100644 (file)
@@ -75,7 +75,7 @@ class AjaxLoginHandler {
         */
        protected function hasLoginBeenProcessed() {
                $loginFormData = $GLOBALS['BE_USER']->getLoginFormData();
-               return $loginFormData['status'] == 'login' && isset($loginFormData['uname']) && isset($loginFormData['uident']) && isset($loginFormData['chalvalue']) && (string) $_COOKIE[\TYPO3\CMS\Core\Authentication\BackendUserAuthentication::getCookieName()] !== (string) $GLOBALS['BE_USER']->id;
+               return $loginFormData['status'] === 'login' && !empty($loginFormData['uname']) && !empty($loginFormData['uident']);
        }
 
        /**
index b04bff4..699bd12 100644 (file)
@@ -105,6 +105,11 @@ class BackendController {
                $this->pageRenderer->addExtDirectCode();
                $this->pageRenderer->addInlineSetting('ModuleMenu.getData', 'ajaxUrl', BackendUtility::getAjaxUrl('ModuleMenu::getData'));
                $this->pageRenderer->addInlineSetting('ModuleMenu.saveMenuState', 'ajaxUrl', BackendUtility::getAjaxUrl('ModuleMenu::saveMenuState'));
+               $this->pageRenderer->addInlineSetting('BackendLogin.BackendLogin::login', 'ajaxUrl', BackendUtility::getAjaxUrl('BackendLogin::login'));
+               $this->pageRenderer->addInlineSetting('BackendLogin.BackendLogin::logout', 'ajaxUrl', BackendUtility::getAjaxUrl('BackendLogin::logout'));
+               $this->pageRenderer->addInlineSetting('BackendLogin.BackendLogin::refreshLogin', 'ajaxUrl', BackendUtility::getAjaxUrl('BackendLogin::refreshLogin'));
+               $this->pageRenderer->addInlineSetting('BackendLogin.BackendLogin::isTimedOut', 'ajaxUrl', BackendUtility::getAjaxUrl('BackendLogin::isTimedOut'));
+               $this->pageRenderer->addInlineSetting('BackendLogin.BackendLogin::getChallenge', 'ajaxUrl', BackendUtility::getAjaxUrl('BackendLogin::getChallenge'));
                // Add default BE javascript
                $this->js = '';
                $this->jsFiles = array(
index fe8dff2..4d08a67 100644 (file)
@@ -3032,11 +3032,13 @@ class BackendUtility {
                } else {
                        $backPath = isset($GLOBALS['BACK_PATH']) ? $GLOBALS['BACK_PATH'] : '';
                }
-               $urlParameters = array(
-                       'ajaxID' => $ajaxIdentifier,
-                       'ajaxToken' => \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get()->generateToken('ajaxCall', $ajaxIdentifier)
-               ) + $urlParameters;
-               $url = 'ajax.php?' . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters, '', TRUE, TRUE), '&');
+               $additionalUrlParameters = array(
+                       'ajaxID' => $ajaxIdentifier
+               );
+               if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['AJAX'][$ajaxIdentifier]['csrfTokenCheck'])) {
+                       $additionalUrlParameters['ajaxToken'] = \TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get()->generateToken('ajaxCall', $ajaxIdentifier);
+               }
+               $url = 'ajax.php?' . ltrim(GeneralUtility::implodeArrayForUrl('', ($additionalUrlParameters + $urlParameters), '', TRUE, TRUE), '&');
                if ($returnAbsoluteUrl) {
                        return GeneralUtility::getIndpEnv('TYPO3_REQUEST_DIR') . $url;
                } else {
index 816fece..f6fd15a 100644 (file)
@@ -41,12 +41,11 @@ Ext.ux.TYPO3.loginRefresh = Ext.extend(Ext.util.Observable, {
                        run: function(){
                                // interval run
                                Ext.Ajax.request({
-                                       url: "ajax.php",
+                                       url: TYPO3.settings.BackendLogin['BackendLogin::isTimedOut'].ajaxUrl,
                                        params: {
-                                               "ajaxID": "BackendLogin::isTimedOut",
-                                               "skipSessionUpdate": 1
+                                               'skipSessionUpdate': 1
                                        },
-                                       method: "GET",
+                                       method: 'GET',
                                        success: function(response, options) {
                                                var result = Ext.util.JSON.decode(response.responseText);
                                                if (result.login.locked) {
@@ -64,8 +63,8 @@ Ext.ux.TYPO3.loginRefresh = Ext.extend(Ext.util.Observable, {
                                                                Ext.MessageBox.hide();
                                                        }
                                                }
-                                               if ((result.login.timed_out || result.login.will_time_out) && Ext.getCmp("loginformWindow")) {
-                                                       Ext.getCmp("login_username").value = TYPO3.configuration.username;
+                                               if ((result.login.timed_out || result.login.will_time_out) && Ext.getCmp('loginformWindow')) {
+                                                       Ext.getCmp('login_username').value = TYPO3.configuration.username;
                                                        this.stopTimer();
                                                        if (result.login.timed_out) {
                                                                this.showLoginForm();
@@ -89,38 +88,38 @@ Ext.ux.TYPO3.loginRefresh = Ext.extend(Ext.util.Observable, {
 
        initComponents: function() {
                var loginPanel = new Ext.FormPanel({
-                       url: "ajax.php",
-                       id: "loginform",
+                       url: TYPO3.settings.BackendLogin['BackendLogin::login'].ajaxUrl,
+                       id: 'loginform',
                        title: TYPO3.LLL.core.refresh_login_title,
                        defaultType: 'textfield',
                        scope: this,
-                       width: "100%",
-                       bodyStyle: "padding: 5px 5px 3px 5px; border-width: 0; margin-bottom: 7px;",
+                       width: '100%',
+                       bodyStyle: 'padding: 5px 5px 3px 5px; border-width: 0; margin-bottom: 7px;',
 
                        items: [{
-                                       xtype: "panel",
-                                       bodyStyle: "margin-bottom: 7px; border: none;",
+                                       xtype: 'panel',
+                                       bodyStyle: 'margin-bottom: 7px; border: none;',
                                        html: TYPO3.LLL.core.login_expired
                                },{
                                        fieldLabel: TYPO3.LLL.core.refresh_login_password,
-                                       name: "p_field",
+                                       name: 'p_field',
                                        width: 250,
-                                       id: "password",
-                                       inputType: "password"
+                                       id: 'password',
+                                       inputType: 'password'
                                },{
-                                       inputType: "hidden",
-                                       name: "username",
-                                       id: "login_username",
-                                       value: ""
+                                       inputType: 'hidden',
+                                       name: 'username',
+                                       id: 'login_username',
+                                       value: ''
                                },{
-                                       inputType: "hidden",
-                                       name: "userident",
-                                       id: "userident",
-                                       value: ""
+                                       inputType: 'hidden',
+                                       name: 'userident',
+                                       id: 'userident',
+                                       value: ''
                                }, {
-                                       inputType: "hidden",
-                                       name: "challenge",
-                                       id: "challenge",
+                                       inputType: 'hidden',
+                                       name: 'challenge',
+                                       id: 'challenge',
                                        value: ''
                                }
                        ],
@@ -137,12 +136,12 @@ Ext.ux.TYPO3.loginRefresh = Ext.extend(Ext.util.Observable, {
                                text: TYPO3.LLL.core.refresh_logout_button,
                                formBind: true,
                                handler: function() {
-                                       top.location.href = TYPO3.configuration.siteUrl + TYPO3.configuration.TYPO3_mainDir + "logout.php";
+                                       top.location.href = TYPO3.configuration.siteUrl + TYPO3.configuration.TYPO3_mainDir + 'logout.php';
                                }
                        }]
                });
                this.loginRefreshWindow = new Ext.Window({
-                       id: "loginformWindow",
+                       id: 'loginformWindow',
                        width: 450,
                        autoHeight: true,
                        closable: true,
@@ -170,10 +169,10 @@ Ext.ux.TYPO3.loginRefresh = Ext.extend(Ext.util.Observable, {
                        resizable: false,
                        draggable: false,
                        modal: true,
-                       id: "loginRefreshWindow",
+                       id: 'loginRefreshWindow',
                        items: [{
-                                       xtype: "panel",
-                                       bodyStyle: "padding: 5px 5px 3px 5px; border-width: 0; margin-bottom: 7px;",
+                                       xtype: 'panel',
+                                       bodyStyle: 'padding: 5px 5px 3px 5px; border-width: 0; margin-bottom: 7px;',
                                        bodyBorder: false,
                                        autoHeight: true,
                                        autoWidth: true,
@@ -188,11 +187,8 @@ Ext.ux.TYPO3.loginRefresh = Ext.extend(Ext.util.Observable, {
                                text: TYPO3.LLL.core.refresh_login_refresh_button,
                                handler: function() {
                                        var refresh = Ext.Ajax.request({
-                                               url: "ajax.php",
-                                               params: {
-                                                       "ajaxID": "BackendLogin::isTimedOut"
-                                               },
-                                               method: "GET",
+                                               url: TYPO3.settings.BackendLogin['BackendLogin::isTimedOut'].ajaxUrl,
+                                               method: 'GET',
                                                scope: this
                                        });
                                        TYPO3.loginRefresh.progressWindow.hide();
@@ -202,7 +198,7 @@ Ext.ux.TYPO3.loginRefresh = Ext.extend(Ext.util.Observable, {
                        }, {
                                text: TYPO3.LLL.core.refresh_direct_logout_button,
                                handler: function() {
-                                       top.location.href = TYPO3.configuration.siteUrl + TYPO3.configuration.TYPO3_mainDir + "logout.php";
+                                       top.location.href = TYPO3.configuration.siteUrl + TYPO3.configuration.TYPO3_mainDir + 'logout.php';
                                }
                        }]
                });
@@ -236,28 +232,25 @@ Ext.ux.TYPO3.loginRefresh = Ext.extend(Ext.util.Observable, {
                if (TYPO3.configuration.showRefreshLoginPopup) {
                        //log off for sure
                        Ext.Ajax.request({
-                               url: "ajax.php",
-                               params: {
-                               "ajaxID": "BackendLogin::logout"
-                       },
-                       method: "GET",
-                       scope: this,
-                       success: function(response, opts) {
-                               TYPO3.loginRefresh.showLoginPopup();
-                       },
-                       failure: function(response, opts) {
-                               alert("something went wrong");
-                       }
+                               url: TYPO3.settings.BackendLogin['BackendLogin::logout'].ajaxUrl,
+                               method: 'GET',
+                               scope: this,
+                               success: function(response, opts) {
+                                       TYPO3.loginRefresh.showLoginPopup();
+                               },
+                               failure: function(response, opts) {
+                                       alert('something went wrong');
+                               }
                        });
                } else {
-                       Ext.getCmp("loginRefreshWindow").hide();
-                       Ext.getCmp("loginformWindow").show();
+                       Ext.getCmp('loginRefreshWindow').hide();
+                       Ext.getCmp('loginformWindow').show();
                }
        },
 
        showLoginPopup: function() {
-               Ext.getCmp("loginRefreshWindow").hide();
-               var vHWin = window.open("login_frameset.php","relogin_" + TS.uniqueID,"height=450,width=700,status=0,menubar=0,location=1");
+               Ext.getCmp('loginRefreshWindow').hide();
+               var vHWin = window.open('login_frameset.php','relogin_' + TYPO3.configuration.uniqueID,'height=450,width=700,status=0,menubar=0,location=1');
                vHWin.focus();
        },
 
@@ -269,42 +262,46 @@ Ext.ux.TYPO3.loginRefresh = Ext.extend(Ext.util.Observable, {
                Ext.TaskMgr.stop(this.loadingTask);
        },
 
-       submitForm: function(challenge) {
-               var form = Ext.getCmp("loginform").getForm();
+       submitForm: function(parameters) {
+               var form = Ext.getCmp('loginform').getForm();
                var fields = form.getValues();
-               if (fields.p_field === "") {
+               if (fields.p_field === '') {
                        Ext.Msg.alert(TYPO3.LLL.core.refresh_login_failed, TYPO3.LLL.core.refresh_login_emptyPassword);
                } else {
-                       if (TS.securityLevel === "superchallenged") {
+                       if (TYPO3.configuration.securityLevel === 'superchallenged') {
                                fields.p_field = MD5(fields.p_field);
                        }
-                       if (TS.securityLevel === "superchallenged" || TS.securityLevel === "challenged") {
-                               fields.challenge = challenge;
-                               fields.userident = MD5(fields.username + ":" + fields.p_field + ":" + challenge);
+                       if (TYPO3.configuration.securityLevel === 'superchallenged' || TYPO3.configuration.securityLevel === 'challenged') {
+                               fields.challenge = parameters.challenge;
+                               fields.userident = MD5(fields.username + ':' + fields.p_field + ':' + parameters.challenge);
+                       } else if (TYPO3.configuration.securityLevel === 'rsa') {
+                               var rsa = new RSAKey();
+                               rsa.setPublic(parameters.publicKeyModulus, parameters.exponent);
+                               var encryptedPassword = rsa.encrypt(fields.p_field);
+                               fields.userident = 'rsa:' + hex2b64(encryptedPassword);
                        } else {
                                fields.userident = fields.p_field;
                        }
-                       fields.p_field =  "";
+                       fields.p_field =  '';
                        form.setValues(fields);
 
                        form.submit({
-                               method: "POST",
+                               method: 'POST',
                                waitTitle: TYPO3.LLL.core.waitTitle,
-                               waitMsg: " ",
+                               waitMsg: ' ',
                                params: {
-                                       "ajaxID": "BackendLogin::login",
-                                       "login_status": "login"
+                                       'login_status': 'login'
                                },
                                success: function(form, action) {
-                                       // response object is "login" so real result will be available in failure handler
-                                       Ext.getCmp("loginformWindow").hide();
+                                       // response object is 'login' so real result will be available in failure handler
+                                       Ext.getCmp('loginformWindow').hide();
                                        TYPO3.loginRefresh.startTimer();
                                },
                                failure: function(form, action) {
                                        var result = Ext.util.JSON.decode(action.response.responseText).login;
                                        if (result.success) {
                                                // User is logged in
-                                               Ext.getCmp("loginformWindow").hide();
+                                               Ext.getCmp('loginformWindow').hide();
                                                TYPO3.loginRefresh.startTimer();
                                        } else {
                                                // TODO: add failure to notification system instead of alert
@@ -316,11 +313,10 @@ Ext.ux.TYPO3.loginRefresh = Ext.extend(Ext.util.Observable, {
        },
 
        triggerSubmitForm: function() {
-               if (TS.securityLevel === 'superchallenged' || TS.securityLevel === 'challenged') {
+               if (TYPO3.configuration.securityLevel === 'superchallenged' || TYPO3.configuration.securityLevel === 'challenged') {
                        Ext.Ajax.request({
-                               url: 'ajax.php',
+                               url: TYPO3.settings.BackendLogin['BackendLogin::getChallenge'].ajaxUrl,
                                params: {
-                                       'ajaxID': 'BackendLogin::getChallenge',
                                        'skipSessionUpdate': 1
                                },
                                method: 'GET',
@@ -328,19 +324,32 @@ Ext.ux.TYPO3.loginRefresh = Ext.extend(Ext.util.Observable, {
                                        var result = Ext.util.JSON.decode(response.responseText);
                                        if (result.challenge) {
                                                Ext.getCmp('challenge').value = result.challenge;
-                                               TYPO3.loginRefresh.submitForm(result.challenge);
+                                               TYPO3.loginRefresh.submitForm(result);
+                                       }
+                               },
+                               scope: this
+                       });
+               } else if (TYPO3.configuration.securityLevel === 'rsa') {
+                       Ext.Ajax.request({
+                               url: TYPO3.settings.BackendLogin['BackendLogin::getRsaPublicKey'].ajaxUrl,
+                               params: {
+                                       'skipSessionUpdate': 1
+                               },
+                               method: 'GET',
+                               success: function(response) {
+                                       var result = Ext.util.JSON.decode(response.responseText);
+                                       if (result.publicKeyModulus && result.exponent) {
+                                               TYPO3.loginRefresh.submitForm(result);
                                        }
                                },
                                scope: this
                        });
                } else {
-                       this.submitForm();
+                       TYPO3.loginRefresh.submitForm();
                }
        }
 });
 
-
-
 /**
  * Initialize login expiration warning object
  */
index 2a2aeb6..9c39a0f 100644 (file)
@@ -44,11 +44,13 @@ abstract class AbstractFormProtection {
        protected $sessionToken;
 
        /**
-        * Constructor. Makes sure the session token is read and
-        * available for checking.
+        * @return string
         */
-       public function __construct() {
-               $this->retrieveSessionToken();
+       protected function getSessionToken() {
+               if ($this->sessionToken === NULL) {
+                       $this->sessionToken = $this->retrieveSessionToken();
+               }
+               return $this->sessionToken;
        }
 
        /**
@@ -81,12 +83,13 @@ abstract class AbstractFormProtection {
         * @param string $action
         * @param string $formInstanceName
         * @return string the 32-character hex ID of the generated token
+        * @throws \InvalidArgumentException
         */
        public function generateToken($formName, $action = '', $formInstanceName = '') {
                if ($formName == '') {
                        throw new \InvalidArgumentException('$formName must not be empty.', 1294586643);
                }
-               $tokenId = \TYPO3\CMS\Core\Utility\GeneralUtility::hmac($formName . $action . $formInstanceName . $this->sessionToken);
+               $tokenId = \TYPO3\CMS\Core\Utility\GeneralUtility::hmac($formName . $action . $formInstanceName . $this->getSessionToken());
                return $tokenId;
        }
 
@@ -101,7 +104,7 @@ abstract class AbstractFormProtection {
         * @return boolean
         */
        public function validateToken($tokenId, $formName, $action = '', $formInstanceName = '') {
-               $validTokenId = \TYPO3\CMS\Core\Utility\GeneralUtility::hmac(((string) $formName . (string) $action) . (string) $formInstanceName . $this->sessionToken);
+               $validTokenId = \TYPO3\CMS\Core\Utility\GeneralUtility::hmac(((string) $formName . (string) $action) . (string) $formInstanceName . $this->getSessionToken());
                if ((string) $tokenId === $validTokenId) {
                        $isValid = TRUE;
                } else {
index 8c8e23b..b281449 100644 (file)
@@ -109,7 +109,6 @@ class BackendFormProtection extends \TYPO3\CMS\Core\FormProtection\AbstractFormP
                        throw new \TYPO3\CMS\Core\Error\Exception('A back-end form protection may only be instantiated if there' . ' is an active back-end session.', 1285067843);
                }
                $this->backendUser = $GLOBALS['BE_USER'];
-               parent::__construct();
        }
 
        /**
@@ -147,7 +146,7 @@ class BackendFormProtection extends \TYPO3\CMS\Core\FormProtection\AbstractFormP
        /**
         * Retrieves the saved session token or generates a new one.
         *
-        * @return array<array>
+        * @return string
         */
        protected function retrieveSessionToken() {
                $this->sessionToken = $this->backendUser->getSessionData('formSessionToken');
@@ -155,6 +154,7 @@ class BackendFormProtection extends \TYPO3\CMS\Core\FormProtection\AbstractFormP
                        $this->sessionToken = $this->generateSessionToken();
                        $this->persistSessionToken();
                }
+               return $this->sessionToken;
        }
 
        /**
@@ -174,6 +174,7 @@ class BackendFormProtection extends \TYPO3\CMS\Core\FormProtection\AbstractFormP
         *
         * @access private
         * @return string
+        * @throws \UnexpectedValueException
         */
        public function setSessionTokenFromRegistry() {
                $this->sessionToken = $this->getRegistry()->get('core', 'formSessionToken:' . $this->backendUser->user['uid']);
@@ -191,17 +192,16 @@ class BackendFormProtection extends \TYPO3\CMS\Core\FormProtection\AbstractFormP
         * @return void
         */
        public function storeSessionTokenInRegistry() {
-               $this->getRegistry()->set('core', 'formSessionToken:' . $this->backendUser->user['uid'], $this->sessionToken);
+               $this->getRegistry()->set('core', 'formSessionToken:' . $this->backendUser->user['uid'], $this->getSessionToken());
        }
 
        /**
         * Removes the session token for the user from the registry.
         *
         * @access private
-        * @return string
         */
        public function removeSessionTokenFromRegistry() {
-               return $this->getRegistry()->remove('core', 'formSessionToken:' . $this->backendUser->user['uid']);
+               $this->getRegistry()->remove('core', 'formSessionToken:' . $this->backendUser->user['uid']);
        }
 
        /**
index a9520f4..edfcfe1 100644 (file)
@@ -50,7 +50,11 @@ class DisabledFormProtection extends \TYPO3\CMS\Core\FormProtection\AbstractForm
         * Disable parent method.
         * Always return TRUE.
         *
-        * @return boolean
+        * @param string $tokenId
+        * @param string $formName
+        * @param string $action
+        * @param string $formInstanceName
+        * @return bool
         */
        public function validateToken($tokenId, $formName, $action = '', $formInstanceName = '') {
                return TRUE;
index 8332a19..8e1d2dc 100644 (file)
@@ -34,10 +34,10 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
        /**
         * @var \TYPO3\CMS\Core\Tests\Unit\FormProtection\Fixtures\FormProtectionTesting
         */
-       protected $fixture;
+       protected $subject;
 
        public function setUp() {
-               $this->fixture = new \TYPO3\CMS\Core\Tests\Unit\FormProtection\Fixtures\FormProtectionTesting();
+               $this->subject = new \TYPO3\CMS\Core\Tests\Unit\FormProtection\Fixtures\FormProtectionTesting();
        }
 
        /////////////////////////////////////////
@@ -46,11 +46,21 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
        /**
         * @test
         */
-       public function constructionRetrievesToken() {
-               $className = uniqid('FormProtection');
-               eval('class ' . $className . ' extends \TYPO3\CMS\Core\Tests\Unit\FormProtection\Fixtures\FormProtectionTesting {' . 'public $tokenHasBeenRetrieved = FALSE; ' . 'protected function retrieveSessionToken() {' . '$this->tokenHasBeenRetrieved = TRUE;' . '}' . '}');
-               $fixture = new $className();
-               $this->assertTrue($fixture->tokenHasBeenRetrieved);
+       public function generateTokenRetrievesTokenOnce() {
+               $subject = $this->getMock('TYPO3\\CMS\\Core\\Tests\\Unit\\FormProtection\\Fixtures\\FormProtectionTesting', array('retrieveSessionToken'));
+               $subject->expects($this->once())->method('retrieveSessionToken')->will($this->returnValue('token'));
+               $subject->generateToken('foo');
+               $subject->generateToken('foo');
+       }
+
+       /**
+        * @test
+        */
+       public function validateTokenRetrievesTokenOnce() {
+               $subject = $this->getMock('TYPO3\\CMS\\Core\\Tests\\Unit\\FormProtection\\Fixtures\\FormProtectionTesting', array('retrieveSessionToken'));
+               $subject->expects($this->once())->method('retrieveSessionToken')->will($this->returnValue('token'));
+               $subject->validateToken('foo', 'bar');
+               $subject->validateToken('foo', 'bar');
        }
 
        /**
@@ -58,18 +68,18 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         */
        public function cleanMakesTokenInvalid() {
                $formName = 'foo';
-               $tokenId = $this->fixture->generateToken($formName);
-               $this->fixture->clean();
-               $this->assertFalse($this->fixture->validateToken($tokenId, $formName));
+               $tokenId = $this->subject->generateToken($formName);
+               $this->subject->clean();
+               $this->assertFalse($this->subject->validateToken($tokenId, $formName));
        }
 
        /**
         * @test
         */
        public function cleanPersistsToken() {
-               $fixture = $this->getMock('TYPO3\\CMS\\Core\\Tests\\Unit\\FormProtection\\Fixtures\\FormProtectionTesting', array('persistSessionToken'));
-               $fixture->expects($this->once())->method('persistSessionToken');
-               $fixture->clean();
+               $subject = $this->getMock('TYPO3\\CMS\\Core\\Tests\\Unit\\FormProtection\\Fixtures\\FormProtectionTesting', array('persistSessionToken'));
+               $subject->expects($this->once())->method('persistSessionToken');
+               $subject->clean();
        }
 
        ///////////////////////////////////
@@ -80,42 +90,42 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         */
        public function generateTokenFormForEmptyFormNameThrowsException() {
                $this->setExpectedException('InvalidArgumentException', '$formName must not be empty.');
-               $this->fixture->generateToken('', 'edit', 'bar');
+               $this->subject->generateToken('', 'edit', 'bar');
        }
 
        /**
         * @test
         */
        public function generateTokenFormForEmptyActionNotThrowsException() {
-               $this->fixture->generateToken('foo', '', '42');
+               $this->subject->generateToken('foo', '', '42');
        }
 
        /**
         * @test
         */
        public function generateTokenFormForEmptyFormInstanceNameNotThrowsException() {
-               $this->fixture->generateToken('foo', 'edit', '');
+               $this->subject->generateToken('foo', 'edit', '');
        }
 
        /**
         * @test
         */
        public function generateTokenFormForOmittedActionAndFormInstanceNameNotThrowsException() {
-               $this->fixture->generateToken('foo');
+               $this->subject->generateToken('foo');
        }
 
        /**
         * @test
         */
        public function generateTokenReturns32CharacterHexToken() {
-               $this->assertRegexp('/^[0-9a-f]{40}$/', $this->fixture->generateToken('foo'));
+               $this->assertRegexp('/^[0-9a-f]{40}$/', $this->subject->generateToken('foo'));
        }
 
        /**
         * @test
         */
        public function generateTokenCalledTwoTimesWithSameParametersReturnsSameTokens() {
-               $this->assertEquals($this->fixture->generateToken('foo', 'edit', 'bar'), $this->fixture->generateToken('foo', 'edit', 'bar'));
+               $this->assertEquals($this->subject->generateToken('foo', 'edit', 'bar'), $this->subject->generateToken('foo', 'edit', 'bar'));
        }
 
        ///////////////////////////////////
@@ -125,14 +135,14 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         * @test
         */
        public function validateTokenWithFourEmptyParametersNotThrowsException() {
-               $this->fixture->validateToken('', '', '', '');
+               $this->subject->validateToken('', '', '', '');
        }
 
        /**
         * @test
         */
        public function validateTokenWithTwoEmptyAndTwoMissingParametersNotThrowsException() {
-               $this->fixture->validateToken('', '');
+               $this->subject->validateToken('', '');
        }
 
        /**
@@ -142,7 +152,7 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $formName = 'foo';
                $action = 'edit';
                $formInstanceName = 'bar';
-               $this->assertTrue($this->fixture->validateToken($this->fixture->generateToken($formName, $action, $formInstanceName), $formName, $action, $formInstanceName));
+               $this->assertTrue($this->subject->validateToken($this->subject->generateToken($formName, $action, $formInstanceName), $formName, $action, $formInstanceName));
        }
 
        /**
@@ -150,7 +160,7 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
         */
        public function validateTokenWithDataFromGenerateTokenWithMissingActionAndFormInstanceNameReturnsTrue() {
                $formName = 'foo';
-               $this->assertTrue($this->fixture->validateToken($this->fixture->generateToken($formName), $formName));
+               $this->assertTrue($this->subject->validateToken($this->subject->generateToken($formName), $formName));
        }
 
        /**
@@ -160,9 +170,9 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $formName = 'foo';
                $action = 'edit';
                $formInstanceName = 'bar';
-               $tokenId = $this->fixture->generateToken($formName, $action, $formInstanceName);
-               $this->fixture->validateToken($tokenId, $formName, $action, $formInstanceName);
-               $this->assertTrue($this->fixture->validateToken($tokenId, $formName, $action, $formInstanceName));
+               $tokenId = $this->subject->generateToken($formName, $action, $formInstanceName);
+               $this->subject->validateToken($tokenId, $formName, $action, $formInstanceName);
+               $this->assertTrue($this->subject->validateToken($tokenId, $formName, $action, $formInstanceName));
        }
 
        /**
@@ -172,8 +182,8 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $formName = 'foo';
                $action = 'edit';
                $formInstanceName = 'bar';
-               $this->fixture->generateToken($formName, $action, $formInstanceName);
-               $this->assertFalse($this->fixture->validateToken('Hello world!', $formName, $action, $formInstanceName));
+               $this->subject->generateToken($formName, $action, $formInstanceName);
+               $this->assertFalse($this->subject->validateToken('Hello world!', $formName, $action, $formInstanceName));
        }
 
        /**
@@ -183,8 +193,8 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $formName = 'foo';
                $action = 'edit';
                $formInstanceName = 'bar';
-               $tokenId = $this->fixture->generateToken($formName, $action, $formInstanceName);
-               $this->assertFalse($this->fixture->validateToken($tokenId, 'espresso', $action, $formInstanceName));
+               $tokenId = $this->subject->generateToken($formName, $action, $formInstanceName);
+               $this->assertFalse($this->subject->validateToken($tokenId, 'espresso', $action, $formInstanceName));
        }
 
        /**
@@ -194,8 +204,8 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $formName = 'foo';
                $action = 'edit';
                $formInstanceName = 'bar';
-               $tokenId = $this->fixture->generateToken($formName, $action, $formInstanceName);
-               $this->assertFalse($this->fixture->validateToken($tokenId, $formName, 'delete', $formInstanceName));
+               $tokenId = $this->subject->generateToken($formName, $action, $formInstanceName);
+               $this->assertFalse($this->subject->validateToken($tokenId, $formName, 'delete', $formInstanceName));
        }
 
        /**
@@ -205,53 +215,53 @@ class AbstractFormProtectionTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $formName = 'foo';
                $action = 'edit';
                $formInstanceName = 'bar';
-               $tokenId = $this->fixture->generateToken($formName, $action, $formInstanceName);
-               $this->assertFalse($this->fixture->validateToken($tokenId, $formName, $action, 'beer'));
+               $tokenId = $this->subject->generateToken($formName, $action, $formInstanceName);
+               $this->assertFalse($this->subject->validateToken($tokenId, $formName, $action, 'beer'));
        }
 
        /**
         * @test
         */
        public function validateTokenForValidTokenNotCallsCreateValidationErrorMessage() {
-               /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\Unit\FormProtection\Fixtures\FormProtectionTesting $fixture */
-               $fixture = $this->getMock('TYPO3\\CMS\\Core\\Tests\\Unit\\FormProtection\\Fixtures\\FormProtectionTesting', array('createValidationErrorMessage'));
-               $fixture->expects($this->never())->method('createValidationErrorMessage');
+               /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\Unit\FormProtection\Fixtures\FormProtectionTesting $subject */
+               $subject = $this->getMock('TYPO3\\CMS\\Core\\Tests\\Unit\\FormProtection\\Fixtures\\FormProtectionTesting', array('createValidationErrorMessage'));
+               $subject->expects($this->never())->method('createValidationErrorMessage');
                $formName = 'foo';
                $action = 'edit';
                $formInstanceName = 'bar';
-               $token = $fixture->generateToken($formName, $action, $formInstanceName);
-               $fixture->validateToken($token, $formName, $action, $formInstanceName);
-               $fixture->__destruct();
+               $token = $subject->generateToken($formName, $action, $formInstanceName);
+               $subject->validateToken($token, $formName, $action, $formInstanceName);
+               $subject->__destruct();
        }
 
        /**
         * @test
         */
        public function validateTokenForInvalidTokenCallsCreateValidationErrorMessage() {
-               /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\Unit\FormProtection\Fixtures\FormProtectionTesting $fixture */
-               $fixture = $this->getMock('TYPO3\\CMS\\Core\\Tests\\Unit\\FormProtection\\Fixtures\\FormProtectionTesting', array('createValidationErrorMessage'));
-               $fixture->expects($this->once())->method('createValidationErrorMessage');
+               /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\Unit\FormProtection\Fixtures\FormProtectionTesting $subject */
+               $subject = $this->getMock('TYPO3\\CMS\\Core\\Tests\\Unit\\FormProtection\\Fixtures\\FormProtectionTesting', array('createValidationErrorMessage'));
+               $subject->expects($this->once())->method('createValidationErrorMessage');
                $formName = 'foo';
                $action = 'edit';
                $formInstanceName = 'bar';
-               $fixture->generateToken($formName, $action, $formInstanceName);
-               $fixture->validateToken('an invalid token ...', $formName, $action, $formInstanceName);
-               $fixture->__destruct();
+               $subject->generateToken($formName, $action, $formInstanceName);
+               $subject->validateToken('an invalid token ...', $formName, $action, $formInstanceName);
+               $subject->__destruct();
        }
 
        /**
         * @test
         */
        public function validateTokenForInvalidFormNameCallsCreateValidationErrorMessage() {
-               /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\Unit\FormProtection\Fixtures\FormProtectionTesting $fixture */
-               $fixture = $this->getMock('TYPO3\\CMS\\Core\\Tests\\Unit\\FormProtection\\Fixtures\\FormProtectionTesting', array('createValidationErrorMessage'));
-               $fixture->expects($this->once())->method('createValidationErrorMessage');
+               /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\Unit\FormProtection\Fixtures\FormProtectionTesting $subject */
+               $subject = $this->getMock('TYPO3\\CMS\\Core\\Tests\\Unit\\FormProtection\\Fixtures\\FormProtectionTesting', array('createValidationErrorMessage'));
+               $subject->expects($this->once())->method('createValidationErrorMessage');
                $formName = 'foo';
                $action = 'edit';
                $formInstanceName = 'bar';
-               $token = $fixture->generateToken($formName, $action, $formInstanceName);
-               $fixture->validateToken($token, 'another form name', $action, $formInstanceName);
-               $fixture->__destruct();
+               $token = $subject->generateToken($formName, $action, $formInstanceName);
+               $subject->validateToken($token, 'another form name', $action, $formInstanceName);
+               $subject->__destruct();
        }
 
 }
index b8a6896..d69c58e 100644 (file)
@@ -47,10 +47,10 @@ class FormProtectionTesting extends \TYPO3\CMS\Core\FormProtection\AbstractFormP
        /**
         * Retrieves all saved tokens.
         *
-        * @return array the saved tokens as a two-dimensional array, will be empty
+        * @return string The saved token
         */
        protected function retrieveSessionToken() {
-               $this->sessionToken = $this->generateSessionToken();
+               return $this->sessionToken = $this->generateSessionToken();
        }
 
        /**
diff --git a/typo3/sysext/rsaauth/Classes/Backend/AjaxLoginHandler.php b/typo3/sysext/rsaauth/Classes/Backend/AjaxLoginHandler.php
new file mode 100644 (file)
index 0000000..d55bdf7
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+namespace TYPO3\CMS\Rsaauth\Backend;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2014 Helmut Hummel <helmut.hummel@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the text file GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Class AjaxLoginHandler
+ */
+class AjaxLoginHandler {
+       /**
+        * Gets RSA Public Key.
+        *
+        * @param array $parameters Parameters (not used)
+        * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler $parent The calling parent AJAX object
+        * @return void
+        */
+       public function getRsaPublicKey(array $parameters, \TYPO3\CMS\Core\Http\AjaxRequestHandler $parent) {
+               $backend = \TYPO3\CMS\Rsaauth\Backend\BackendFactory::getBackend();
+               if ($backend !== NULL) {
+                       $keyPair = $backend->createNewKeyPair();
+                       $storage = \TYPO3\CMS\Rsaauth\Storage\StorageFactory::getStorage();
+                       $storage->put($keyPair->getPrivateKey());
+                       session_commit();
+                       $parent->addContent('publicKeyModulus', $keyPair->getPublicKeyModulus());
+                       $parent->addContent('exponent', sprintf('%x', $keyPair->getExponent()));
+                       $parent->setContentFormat('json');
+               } else {
+                       $parent->setError('No OpenSSL backend could be obtained for rsaauth.');
+               }
+       }
+}
\ No newline at end of file
diff --git a/typo3/sysext/rsaauth/Classes/Hook/BackendHookForAjaxLogin.php b/typo3/sysext/rsaauth/Classes/Hook/BackendHookForAjaxLogin.php
new file mode 100644 (file)
index 0000000..2274af1
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+namespace TYPO3\CMS\Rsaauth\Hook;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2014 Helmut Hummel <helmut.hummel@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+
+/**
+ * This class adds RSA JavaScript to the backend
+ */
+class BackendHookForAjaxLogin {
+       /**
+        * Adds RSA-specific JavaScript
+        *
+        * @param array $configuration
+        * @param \TYPO3\CMS\Backend\Controller\BackendController $backendController
+        * @return void
+        */
+       public function addRsaJsLibraries(array $configuration, \TYPO3\CMS\Backend\Controller\BackendController $backendController) {
+               $javascriptPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('rsaauth') . 'resources/';
+               $files = array(
+                       'jsbn/jsbn.js',
+                       'jsbn/prng4.js',
+                       'jsbn/rng.js',
+                       'jsbn/rsa.js',
+                       'jsbn/base64.js'
+               );
+               foreach ($files as $file) {
+                       $backendController->getPageRenderer()->addJsLibrary($file, $javascriptPath . $file);
+               }
+               $backendController->getPageRenderer()->addInlineSetting('BackendLogin.BackendLogin::getRsaPublicKey', 'ajaxUrl', BackendUtility::getAjaxUrl('BackendLogin::getRsaPublicKey'));
+       }
+}
index 3678853..c5fe3b5 100644 (file)
@@ -28,5 +28,11 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/setup/mod/index.php']['modifyUser
 $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['felogin']['loginFormOnSubmitFuncs'][$_EXTKEY] = 'TYPO3\\CMS\\Rsaauth\\Hook\\FrontendLoginHook->loginFormHook';
 // Add a hook to show Backend warnings
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['displayWarningMessages'][$_EXTKEY] = 'TYPO3\\CMS\\Rsaauth\\BackendWarnings';
-// Use popup window to refresh login instead of the AJAX relogin:
-$TYPO3_CONF_VARS['BE']['showRefreshLoginPopup'] = 1;
+
+\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::registerAjaxHandler(
+       'BackendLogin::getRsaPublicKey',
+       'TYPO3\\CMS\\Rsaauth\\Backend\\AjaxLoginHandler->getRsaPublicKey',
+       FALSE
+);
+
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']['constructPostProcess'][] = 'TYPO3\\CMS\\Rsaauth\\Hook\\BackendHookForAjaxLogin->addRsaJsLibraries';
\ No newline at end of file
index c3b3161..7f40298 100644 (file)
@@ -27,8 +27,6 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/saltedpasswords']['saltMethods']
        'className' => 'TYPO3\\CMS\\Saltedpasswords\\SaltedPasswordService'
 ));
 
-// Use popup window to refresh login instead of the AJAX relogin:
-$TYPO3_CONF_VARS['BE']['showRefreshLoginPopup'] = 1;
 // Register bulk update task
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['TYPO3\\CMS\\Saltedpasswords\\Task\\BulkUpdateTask'] = array(
        'extension' => $_EXTKEY,