From 2e76c426b9e66ece90cee803765dda81b754c195 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Fri, 4 Sep 2020 11:53:40 +0200 Subject: [PATCH 01/20] [TASK] Extend request bundle model --- Classes/Backend/ConfirmationFactory.php | 9 ++++ Classes/Backend/RequestMetaData.php | 60 +++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/Classes/Backend/ConfirmationFactory.php b/Classes/Backend/ConfirmationFactory.php index 4478873..eedc1f7 100644 --- a/Classes/Backend/ConfirmationFactory.php +++ b/Classes/Backend/ConfirmationFactory.php @@ -100,9 +100,18 @@ public function createRequestFromArray(array $data): ConfirmationRequest public function createRequestMetaDataFromArray(array $data): RequestMetaData { $target = GeneralUtility::makeInstance(RequestMetaData::class); + if (isset($data['scope'])) { + $target = $target->withScope($data['scope']); + } if (isset($data['returnUrl'])) { $target = $target->withReturnUrl($data['returnUrl']); } + if (isset($data['eventName'])) { + $target = $target->withEventName($data['eventName']); + } + if (isset($data['jsonData'])) { + $target = $target->withJsonData($data['jsonData']); + } return $target; } } diff --git a/Classes/Backend/RequestMetaData.php b/Classes/Backend/RequestMetaData.php index c058253..f2930e0 100644 --- a/Classes/Backend/RequestMetaData.php +++ b/Classes/Backend/RequestMetaData.php @@ -17,16 +17,38 @@ class RequestMetaData implements \JsonSerializable { + /** + * @var string|null + */ + protected $scope; + /** * @var string|null */ protected $returnUrl; + /** + * @var string|null + */ + protected $eventName; + + /** + * @var array|null + */ + protected $jsonData; + public function jsonSerialize(): array { return get_object_vars($this); } + public function withScope(string $scope): self + { + $target = clone $this; + $target->scope = $scope; + return $target; + } + public function withReturnUrl(string $returnUrl): self { $target = clone $this; @@ -34,6 +56,28 @@ public function withReturnUrl(string $returnUrl): self return $target; } + public function withEventName(string $eventName): self + { + $target = clone $this; + $target->eventName = $eventName; + return $target; + } + + public function withJsonData(array $jsonData): self + { + $target = clone $this; + $target->jsonData = $jsonData; + return $target; + } + + /** + * @return string|null + */ + public function getScope(): ?string + { + return $this->scope; + } + /** * @return string|null */ @@ -41,4 +85,20 @@ public function getReturnUrl(): ?string { return $this->returnUrl; } + + /** + * @return string|null + */ + public function getEventName(): ?string + { + return $this->eventName; + } + + /** + * @return array|null + */ + public function getJsonData(): ?array + { + return $this->jsonData; + } } From c3a8b6cc18bcf0b99f5135be6df639ba91769e75 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Fri, 4 Sep 2020 11:54:12 +0200 Subject: [PATCH 02/20] [TASK] Add (internal) option to purge bundles --- Classes/Backend/ConfirmationHandler.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Classes/Backend/ConfirmationHandler.php b/Classes/Backend/ConfirmationHandler.php index 2ce8bee..098c8dd 100644 --- a/Classes/Backend/ConfirmationHandler.php +++ b/Classes/Backend/ConfirmationHandler.php @@ -52,6 +52,11 @@ class ConfirmationHandler implements LoggerAwareInterface */ protected $currentTimestamp; + /** + * @var bool + */ + private $immediatePurge = false; + public function __construct(ConfirmationFactory $factory = null, Behavior $behavior = null) { $this->factory = $factory ?? GeneralUtility::makeInstance(ConfirmationFactory::class); @@ -159,7 +164,7 @@ protected function purgeSubjects(AbstractUserAuthentication $user, array $sessio continue; } foreach ($sessionData[$type] as $key => $item) { - if (is_int($item) && $item >= $this->currentTimestamp) { + if (!$this->immediatePurge && is_int($item) && $item >= $this->currentTimestamp) { continue; } unset($sessionData[$type][$key]); @@ -174,7 +179,7 @@ protected function purgeBundles(AbstractUserAuthentication $user, array $session $purged = false; foreach ($sessionData as $key => $item) { $expirationTimestamp = $item['expiration'] ?? 0; - if ($expirationTimestamp >= $this->currentTimestamp) { + if (!$this->immediatePurge && $expirationTimestamp >= $this->currentTimestamp) { continue; } $data = json_decode($item['bundle'] ?? '', true); From 0b46f9cc79fecb939643997c9ba9a1ef1836e4c9 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Fri, 4 Sep 2020 11:56:11 +0200 Subject: [PATCH 03/20] [TASK] Split into HTML and JSON route handlers --- Classes/Backend/RouteManager.php | 8 +-- ...teHandler.php => CoreHtmlRouteHandler.php} | 16 ++---- Classes/Scope/CoreJsonRouteHandler.php | 54 +++++++++++++++++++ Classes/Scope/RouteHandlerTrait.php | 31 +++++++++++ 4 files changed, 95 insertions(+), 14 deletions(-) rename Classes/Scope/{CoreRouteHandler.php => CoreHtmlRouteHandler.php} (90%) create mode 100644 Classes/Scope/CoreJsonRouteHandler.php create mode 100644 Classes/Scope/RouteHandlerTrait.php diff --git a/Classes/Backend/RouteManager.php b/Classes/Backend/RouteManager.php index 5bb6936..970e744 100644 --- a/Classes/Backend/RouteManager.php +++ b/Classes/Backend/RouteManager.php @@ -15,7 +15,8 @@ * The TYPO3 project - inspiring people to share! */ -use FriendsOfTYPO3\SudoMode\Scope\CoreRouteHandler; +use FriendsOfTYPO3\SudoMode\Scope\CoreHtmlRouteHandler; +use FriendsOfTYPO3\SudoMode\Scope\CoreJsonRouteHandler; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Backend\Routing\Route; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -24,10 +25,11 @@ class RouteManager { /** * @var RouteHandlerInterface[] - // @todo Add possibility to register 3rd party handlers + * @todo Add possibility to register 3rd party handlers */ protected $handlerClassNames = [ - CoreRouteHandler::class, + CoreHtmlRouteHandler::class, + CoreJsonRouteHandler::class, ]; /** diff --git a/Classes/Scope/CoreRouteHandler.php b/Classes/Scope/CoreHtmlRouteHandler.php similarity index 90% rename from Classes/Scope/CoreRouteHandler.php rename to Classes/Scope/CoreHtmlRouteHandler.php index d1e8882..fd1c74e 100644 --- a/Classes/Scope/CoreRouteHandler.php +++ b/Classes/Scope/CoreHtmlRouteHandler.php @@ -22,12 +22,9 @@ use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Core\Utility\GeneralUtility; -class CoreRouteHandler implements RouteHandlerInterface +class CoreHtmlRouteHandler implements RouteHandlerInterface { - /** - * @var \Closure[] - */ - protected $handlers; + use RouteHandlerTrait; public function __construct() { @@ -71,11 +68,8 @@ public function resolveMetaData(ServerRequestInterface $request, Route $route): { $routePath = $this->normalizeRoutePath($route); $returnUrl = $this->handlers[$routePath]($route, $request); - return (new RequestMetaData())->withReturnUrl($returnUrl); - } - - protected function normalizeRoutePath(Route $route): string - { - return rtrim($route->getPath(), '/'); + return (new RequestMetaData()) + ->withScope('html') + ->withReturnUrl($returnUrl); } } diff --git a/Classes/Scope/CoreJsonRouteHandler.php b/Classes/Scope/CoreJsonRouteHandler.php new file mode 100644 index 0000000..bc4b890 --- /dev/null +++ b/Classes/Scope/CoreJsonRouteHandler.php @@ -0,0 +1,54 @@ +handlers = [ + '/ajax/record/process' => function(Route $route, ServerRequestInterface $request, RequestMetaData $metaData): RequestMetaData { + return $metaData + ->withEventName('sudo-mode:confirmation-request') + ->withJsonData([]); + }, + ]; + + // TYPO3 v9 + // $this->handlers['/user/setup'] = $this->handlers['/module/user/setup']; + } + + public function canHandle(ServerRequestInterface $request, Route $route): bool + { + $routePath = $this->normalizeRoutePath($route); + return in_array($routePath, array_keys($this->handlers), true); + } + + public function resolveMetaData(ServerRequestInterface $request, Route $route): RequestMetaData + { + $routePath = $this->normalizeRoutePath($route); + $metaData = (new RequestMetaData())->withScope('json'); + return $this->handlers[$routePath]($route, $request, $metaData); + } +} diff --git a/Classes/Scope/RouteHandlerTrait.php b/Classes/Scope/RouteHandlerTrait.php new file mode 100644 index 0000000..7dc8497 --- /dev/null +++ b/Classes/Scope/RouteHandlerTrait.php @@ -0,0 +1,31 @@ +getPath(), '/'); + } +} From 4df252c00e8e2d4d9b7456672da4519e5683321d Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Fri, 4 Sep 2020 11:59:41 +0200 Subject: [PATCH 04/20] [TASK] Add LanguageService to controller --- Classes/Backend/ConfirmationController.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Classes/Backend/ConfirmationController.php b/Classes/Backend/ConfirmationController.php index 394aa1e..17cead9 100644 --- a/Classes/Backend/ConfirmationController.php +++ b/Classes/Backend/ConfirmationController.php @@ -28,6 +28,7 @@ use TYPO3\CMS\Core\Http\HtmlResponse; use TYPO3\CMS\Core\Http\JsonResponse; use TYPO3\CMS\Core\Http\RedirectResponse; +use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; @@ -43,6 +44,8 @@ class ConfirmationController implements LoggerAwareInterface use LoggerAwareTrait; use LoggerAccessorTrait; + private const LANGUAGE_PREFIX = 'LLL:EXT:sudo_mode/Resources/Private/Language/locallang.xlf'; + protected const FLAG_INVALID_PASSWORD = 1; /** @@ -60,10 +63,21 @@ class ConfirmationController implements LoggerAwareInterface */ protected $uriBuilder; - public function __construct(ConfirmationHandler $handler = null, BackendUserAuthentication $user = null, UriBuilder $uriBuilder = null) + /** + * @var LanguageService + */ + protected $languageService; + + public function __construct( + ConfirmationHandler $handler = null, + BackendUserAuthentication $user = null, + UriBuilder $uriBuilder = null, + LanguageService $languageService = null + ) { $this->handler = $handler ?? GeneralUtility::makeInstance(ConfirmationHandler::class); $this->uriBuilder = $uriBuilder ?? GeneralUtility::makeInstance(UriBuilder::class); + $this->languageService = $languageService ?? GeneralUtility::makeInstance(LanguageService::class); $this->user = $user ?? $GLOBALS['BE_USER']; } @@ -281,5 +295,9 @@ protected function getAuthServices(string $subType, array $loginData, array $aut protected function isJsonRequest(ServerRequestInterface $request): bool { return strpos($request->getHeaderLine('content-type'), 'application/json') === 0; + + private function resolveLabel(string $identifier): string + { + return $this->languageService->sL(self::LANGUAGE_PREFIX . ':' . $identifier); } } From 93d72fd5380ac6b16fc876ba326d33ec7f06349b Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Fri, 4 Sep 2020 12:00:38 +0200 Subject: [PATCH 05/20] [TASK] Clean up code --- Classes/Backend/ConfirmationController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Classes/Backend/ConfirmationController.php b/Classes/Backend/ConfirmationController.php index 17cead9..eb24cc3 100644 --- a/Classes/Backend/ConfirmationController.php +++ b/Classes/Backend/ConfirmationController.php @@ -170,7 +170,9 @@ protected function cancelAction(ServerRequestInterface $request, ConfirmationBun protected function errorAction(ServerRequestInterface $request): ResponseInterface { $view = $this->createView('Error'); - $view->assign('returnUrl', $request->getQueryParams()['returnUrl'] ?? ''); + $view->assignMultiple([ + 'returnUrl' => $request->getQueryParams()['returnUrl'] ?? '', + ]); return new HtmlResponse($view->render()); } From 3a9424fdc183e8828465b3eafcef0e5ca0809bc2 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Fri, 4 Sep 2020 12:01:39 +0200 Subject: [PATCH 06/20] [TASK] Consider JSON scope in URL generation --- Classes/Backend/ConfirmationController.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Classes/Backend/ConfirmationController.php b/Classes/Backend/ConfirmationController.php index eb24cc3..e99c36c 100644 --- a/Classes/Backend/ConfirmationController.php +++ b/Classes/Backend/ConfirmationController.php @@ -180,18 +180,20 @@ public function buildActionUriFromBundle(string $actionName, ConfirmationBundle { return $this->buildActionUri( $actionName, - $bundle->getRequestMetaData()->getReturnUrl(), $bundle->getIdentifier(), + $bundle->getRequestMetaData()->getReturnUrl(), + $bundle->getRequestMetaData()->getScope(), $flags ); } - protected function buildActionUri(string $actionName, string $returnUrl, string $bundleIdentifier, int $flags = null): UriInterface + protected function buildActionUri(string $actionName, string $bundleIdentifier, string $returnUrl = null, string $scope = null, int $flags = null): UriInterface { $parameters = [ 'action' => $actionName, - 'returnUrl' => $returnUrl, 'bundle' => $bundleIdentifier, + 'returnUrl' => $returnUrl, + 'scope' => $scope, 'flags' => $flags, ]; $parameters['hmac'] = $this->signParameters($parameters); @@ -211,7 +213,7 @@ protected function resolveUriParameters(ServerRequestInterface $request): array protected function filterParameters(array $parameters): array { - return array_intersect_key($parameters, array_flip(['action', 'returnUrl', 'bundle', 'flags', 'hmac'])); + return array_intersect_key($parameters, array_flip(['action', 'bundle', 'returnUrl', 'scope', 'flags', 'hmac'])); } protected function signParameters(array $parameters): string From 2bf33245717ad9ed62b0fd574df5d2d6acd99e17 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Fri, 4 Sep 2020 12:05:32 +0200 Subject: [PATCH 07/20] [TASK] Prepare views for HTML and JSON scopes --- Classes/Backend/ConfirmationController.php | 34 ++++++---- Resources/Private/Layouts/Backend/Module.html | 18 +++++- Resources/Private/Layouts/Backend/None.html | 8 +++ .../Backend/InvalidPasswordMessageBlock.html | 11 ++++ .../Partials/Backend/PasswordInputBlock.html | 19 ++++++ .../Private/Templates/Backend/Error.html | 38 +++++------ .../Private/Templates/Backend/Request.html | 64 +++++++------------ 7 files changed, 116 insertions(+), 76 deletions(-) create mode 100644 Resources/Private/Layouts/Backend/None.html create mode 100644 Resources/Private/Partials/Backend/InvalidPasswordMessageBlock.html create mode 100644 Resources/Private/Partials/Backend/PasswordInputBlock.html diff --git a/Classes/Backend/ConfirmationController.php b/Classes/Backend/ConfirmationController.php index e99c36c..4b834f1 100644 --- a/Classes/Backend/ConfirmationController.php +++ b/Classes/Backend/ConfirmationController.php @@ -104,18 +104,22 @@ public function mainAction(ServerRequestInterface $request): ResponseInterface protected function requestAction(ServerRequestInterface $request, ConfirmationBundle $bundle): ResponseInterface { + $isJsonRequest = $this->isJsonRequest($request); $flags = (int)($request->getQueryParams()['flags'] ?? 0); - if (!$this->isJsonRequest($request)) { - $view = $this->createView('Request'); - $view->assignMultiple([ - 'bundle' => $bundle, - 'verifyUri' => (string)$this->buildActionUriFromBundle('verify', $bundle), - 'flagInvalidPassword' => $flags & self::FLAG_INVALID_PASSWORD, - ]); - if (!empty($bundle->getRequestMetaData()->getReturnUrl())) { - $view->assign('cancelUri', (string)$this->buildActionUriFromBundle('cancel', $bundle)); - } + $view = $this->createView('Request'); + $view->assignMultiple([ + 'bundle' => $bundle, + 'verifyUri' => (string)$this->buildActionUriFromBundle('verify', $bundle), + 'flagInvalidPassword' => $flags & self::FLAG_INVALID_PASSWORD, + 'layout' => $isJsonRequest ? 'None' : 'Module', + 'isJsonRequest' => $isJsonRequest, + ]); + if (!empty($bundle->getRequestMetaData()->getReturnUrl())) { + $view->assign('cancelUri', (string)$this->buildActionUriFromBundle('cancel', $bundle)); + } + + if (!$isJsonRequest) { $this->applyAdditionalJavaScriptModules(); return new HtmlResponse($view->render()); } @@ -298,7 +302,15 @@ protected function getAuthServices(string $subType, array $loginData, array $aut protected function isJsonRequest(ServerRequestInterface $request): bool { - return strpos($request->getHeaderLine('content-type'), 'application/json') === 0; + return strpos($request->getHeaderLine('content-type'), 'application/json') === 0 + || ($request->getQueryParams()['scope'] ?? null) === 'json'; + } + + protected function reduceSpaces(string $value): string + { + $value = preg_replace('#\s{2,}#', ' ', $value); + return trim($value); + } private function resolveLabel(string $identifier): string { diff --git a/Resources/Private/Layouts/Backend/Module.html b/Resources/Private/Layouts/Backend/Module.html index 40bca53..c56bc84 100644 --- a/Resources/Private/Layouts/Backend/Module.html +++ b/Resources/Private/Layouts/Backend/Module.html @@ -5,7 +5,23 @@ - + + + diff --git a/Resources/Private/Layouts/Backend/None.html b/Resources/Private/Layouts/Backend/None.html new file mode 100644 index 0000000..15c3167 --- /dev/null +++ b/Resources/Private/Layouts/Backend/None.html @@ -0,0 +1,8 @@ + + + + + diff --git a/Resources/Private/Partials/Backend/InvalidPasswordMessageBlock.html b/Resources/Private/Partials/Backend/InvalidPasswordMessageBlock.html new file mode 100644 index 0000000..501ec53 --- /dev/null +++ b/Resources/Private/Partials/Backend/InvalidPasswordMessageBlock.html @@ -0,0 +1,11 @@ + + +
+ +
+ + diff --git a/Resources/Private/Partials/Backend/PasswordInputBlock.html b/Resources/Private/Partials/Backend/PasswordInputBlock.html new file mode 100644 index 0000000..edf80a1 --- /dev/null +++ b/Resources/Private/Partials/Backend/PasswordInputBlock.html @@ -0,0 +1,19 @@ + + + +
+
+ + + +
+
+
+ + diff --git a/Resources/Private/Templates/Backend/Error.html b/Resources/Private/Templates/Backend/Error.html index fe9c7d4..3f1ec26 100644 --- a/Resources/Private/Templates/Backend/Error.html +++ b/Resources/Private/Templates/Backend/Error.html @@ -4,34 +4,26 @@ xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers" data-namespace-typo3-fluid="true"> - + - - + + + -