From 30bf9dcc754918d73b32d54d3b9e619527e3fd35 Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Tue, 6 Apr 2021 22:04:56 +0200
Subject: [PATCH] [TASK] Limit GET/POST requests of PersistentStorage

This change limits the GET request for the PersistentStorage
to only "get" and "getAll", where as the others are only working
for "post".

Since this logic is encapsulated properly in the Persistent
JavaScript API, no other parts are affected.

Resolves: #93948
Releases: master
Change-Id: I965ea4552671c743cabed10c4cd8ad7275532420
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/68715
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Oliver Bartsch <bo@cedev.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
---
 .../Public/TypeScript/Storage/Persistent.ts        |  1 +
 .../Classes/Controller/UserSettingsController.php  | 14 +++++++++++++-
 .../Public/JavaScript/Storage/Persistent.js        |  2 +-
 3 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Storage/Persistent.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Storage/Persistent.ts
index 7cd4840b8571..ffb8019d7d86 100644
--- a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Storage/Persistent.ts
+++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Storage/Persistent.ts
@@ -115,6 +115,7 @@ class Persistent {
       data: {
         action: 'clear',
       },
+      method: 'post',
     });
     this.data = false;
   }
diff --git a/typo3/sysext/backend/Classes/Controller/UserSettingsController.php b/typo3/sysext/backend/Classes/Controller/UserSettingsController.php
index ba38cc931a25..f5fd82a1fb3e 100644
--- a/typo3/sysext/backend/Classes/Controller/UserSettingsController.php
+++ b/typo3/sysext/backend/Classes/Controller/UserSettingsController.php
@@ -30,6 +30,11 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  */
 class UserSettingsController
 {
+    private const ALLOWED_ACTIONS = [
+        'GET' => ['get', 'getAll'],
+        'POST' => ['set', 'addToList', 'removeFromList', 'unset', 'clear']
+    ];
+
     /**
      * Processes all AJAX calls and returns a JSON for the data
      *
@@ -39,7 +44,8 @@ class UserSettingsController
     public function processAjaxRequest(ServerRequestInterface $request): ResponseInterface
     {
         // do the regular / main logic, depending on the action parameter
-        $action = $request->getParsedBody()['action'] ?? $request->getQueryParams()['action'] ?? '';
+        $action = $this->getValidActionFromRequest($request);
+
         $key = $request->getParsedBody()['key'] ?? $request->getQueryParams()['key'] ?? '';
         $value = $request->getParsedBody()['value'] ?? $request->getQueryParams()['value'] ?? '';
         $backendUserConfiguration = GeneralUtility::makeInstance(BackendUserConfiguration::class);
@@ -75,4 +81,10 @@ class UserSettingsController
         }
         return (new JsonResponse())->setPayload($content);
     }
+
+    protected function getValidActionFromRequest(ServerRequestInterface $request): string
+    {
+        $action = $request->getParsedBody()['action'] ?? $request->getQueryParams()['action'] ?? '';
+        return in_array($action, (self::ALLOWED_ACTIONS[$request->getMethod()] ?? []), true) ? $action : '';
+    }
 }
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Storage/Persistent.js b/typo3/sysext/backend/Resources/Public/JavaScript/Storage/Persistent.js
index cb67399d2319..74fa11f0b771 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/Storage/Persistent.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Storage/Persistent.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};define(["require","exports","jquery"],(function(t,e,s){"use strict";s=__importDefault(s);return new class{constructor(){this.data=!1,this.get=t=>{const e=this;if(!1===this.data){let s;return this.loadFromServer().done(()=>{s=e.getRecursiveDataByDeepKey(e.data,t.split("."))}),s}return this.getRecursiveDataByDeepKey(this.data,t.split("."))},this.set=(t,e)=>(!1!==this.data&&(this.data=this.setRecursiveDataByDeepKey(this.data,t.split("."),e)),this.storeOnServer(t,e)),this.addToList=(t,e)=>{const a=this;return s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{data:{action:"addToList",key:t,value:e},method:"post"}).done(t=>{a.data=t})},this.removeFromList=(t,e)=>{const a=this;return s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{data:{action:"removeFromList",key:t,value:e},method:"post"}).done(t=>{a.data=t})},this.unset=t=>{const e=this;return s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{data:{action:"unset",key:t},method:"post"}).done(t=>{e.data=t})},this.clear=()=>{s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{data:{action:"clear"}}),this.data=!1},this.isset=t=>{const e=this.get(t);return null!=e},this.load=t=>{this.data=t},this.loadFromServer=()=>{const t=this;return s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{async:!1,data:{action:"getAll"}}).done(e=>{t.data=e})},this.storeOnServer=(t,e)=>{const a=this;return s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{data:{action:"set",key:t,value:e},method:"post"}).done(t=>{a.data=t})},this.getRecursiveDataByDeepKey=(t,e)=>{if(1===e.length)return(t||{})[e[0]];const s=e.shift();return this.getRecursiveDataByDeepKey(t[s]||{},e)},this.setRecursiveDataByDeepKey=(t,e,s)=>{if(1===e.length)(t=t||{})[e[0]]=s;else{const a=e.shift();t[a]=this.setRecursiveDataByDeepKey(t[a]||{},e,s)}return t}}}}));
\ No newline at end of file
+var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};define(["require","exports","jquery"],(function(t,e,s){"use strict";s=__importDefault(s);return new class{constructor(){this.data=!1,this.get=t=>{const e=this;if(!1===this.data){let s;return this.loadFromServer().done(()=>{s=e.getRecursiveDataByDeepKey(e.data,t.split("."))}),s}return this.getRecursiveDataByDeepKey(this.data,t.split("."))},this.set=(t,e)=>(!1!==this.data&&(this.data=this.setRecursiveDataByDeepKey(this.data,t.split("."),e)),this.storeOnServer(t,e)),this.addToList=(t,e)=>{const a=this;return s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{data:{action:"addToList",key:t,value:e},method:"post"}).done(t=>{a.data=t})},this.removeFromList=(t,e)=>{const a=this;return s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{data:{action:"removeFromList",key:t,value:e},method:"post"}).done(t=>{a.data=t})},this.unset=t=>{const e=this;return s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{data:{action:"unset",key:t},method:"post"}).done(t=>{e.data=t})},this.clear=()=>{s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{data:{action:"clear"},method:"post"}),this.data=!1},this.isset=t=>{const e=this.get(t);return null!=e},this.load=t=>{this.data=t},this.loadFromServer=()=>{const t=this;return s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{async:!1,data:{action:"getAll"}}).done(e=>{t.data=e})},this.storeOnServer=(t,e)=>{const a=this;return s.default.ajax(TYPO3.settings.ajaxUrls.usersettings_process,{data:{action:"set",key:t,value:e},method:"post"}).done(t=>{a.data=t})},this.getRecursiveDataByDeepKey=(t,e)=>{if(1===e.length)return(t||{})[e[0]];const s=e.shift();return this.getRecursiveDataByDeepKey(t[s]||{},e)},this.setRecursiveDataByDeepKey=(t,e,s)=>{if(1===e.length)(t=t||{})[e[0]]=s;else{const a=e.shift();t[a]=this.setRecursiveDataByDeepKey(t[a]||{},e,s)}return t}}}}));
\ No newline at end of file
-- 
GitLab