Move ACP authentication check into middleware
authorTim Düsterhus <duesterhus@woltlab.com>
Mon, 8 Aug 2022 12:45:10 +0000 (14:45 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Mon, 8 Aug 2022 12:45:10 +0000 (14:45 +0200)
This is similar to #4935 / 51154ba3f8f1d09b54560d5d1933f9053ef409cb.

wcfsetup/install/files/lib/http/middleware/EnforceAcpAuthentication.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/WCFACP.class.php
wcfsetup/install/files/lib/system/request/RequestHandler.class.php

diff --git a/wcfsetup/install/files/lib/http/middleware/EnforceAcpAuthentication.class.php b/wcfsetup/install/files/lib/http/middleware/EnforceAcpAuthentication.class.php
new file mode 100644 (file)
index 0000000..30de5aa
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+
+namespace wcf\http\middleware;
+
+use Laminas\Diactoros\Response\RedirectResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use wcf\acp\action\FullLogoutAction;
+use wcf\acp\form\LoginForm;
+use wcf\acp\form\MultifactorAuthenticationForm;
+use wcf\acp\form\ReauthenticationForm;
+use wcf\http\Helper;
+use wcf\system\exception\AJAXException;
+use wcf\system\exception\NamedUserException;
+use wcf\system\request\LinkHandler;
+use wcf\system\request\RequestHandler;
+use wcf\system\user\multifactor\TMultifactorRequirementEnforcer;
+use wcf\system\WCF;
+use wcf\system\WCFACP;
+
+/**
+ * Checks all ACP requests for proper authentication.
+ *
+ * @author  Tim Duesterhus
+ * @copyright   2001-2022 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Http\Middleware
+ * @since   6.0
+ */
+final class EnforceAcpAuthentication implements MiddlewareInterface
+{
+    use TMultifactorRequirementEnforcer;
+
+    private const ALLOWED_CONTROLLERS = [
+        LoginForm::class,
+        ReauthenticationForm::class,
+        FullLogoutAction::class,
+        MultifactorAuthenticationForm::class,
+    ];
+
+    /**
+     * @inheritDoc
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        if (!RequestHandler::getInstance()->isACPRequest()) {
+            return $handler->handle($request);
+        }
+
+        if (WCFACP::inRescueMode()) {
+            return $handler->handle($request);
+        }
+
+        $controller = RequestHandler::getInstance()->getActiveRequest()->getClassName();
+        if (\in_array($controller, self::ALLOWED_CONTROLLERS)) {
+            return $handler->handle($request);
+        }
+
+        if (!WCF::getUser()->userID) {
+            return $this->handleGuest($request);
+        }
+
+        if (!WCF::getSession()->getPermission('admin.general.canUseAcp')) {
+            return $this->handleNoAcpPermission($request);
+        }
+
+        if (WCF::getSession()->needsReauthentication()) {
+            return $this->handleReauthentication($request);
+        }
+
+        $this->enforceMultifactorAuthentication();
+
+        // force debug mode if in ACP and authenticated
+        WCFACP::overrideDebugMode();
+
+        return $handler->handle($request);
+    }
+
+    private function handleGuest(ServerRequestInterface $request): ResponseInterface
+    {
+        if (Helper::isAjaxRequest($request)) {
+            throw new AJAXException(
+                WCF::getLanguage()->getDynamicVariable('wcf.ajax.error.sessionExpired'),
+                AJAXException::SESSION_EXPIRED,
+                ''
+            );
+        }
+
+        return new RedirectResponse(
+            LinkHandler::getInstance()->getControllerLink(
+                LoginForm::class,
+                [
+                    'url' => (string)$request->getUri(),
+                ]
+            )
+        );
+    }
+
+    private function handleNoAcpPermission(ServerRequestInterface $request): ResponseInterface
+    {
+        WCF::getTPL()->assign([
+            '__isLogin' => true,
+        ]);
+
+        if (Helper::isAjaxRequest($request)) {
+            throw new AJAXException(
+                WCF::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'),
+                AJAXException::INSUFFICIENT_PERMISSIONS
+            );
+        } else {
+            throw new NamedUserException(
+                WCF::getLanguage()->getDynamicVariable('wcf.user.username.error.acpNotAuthorized')
+            );
+        }
+    }
+
+    private function handleReauthentication(ServerRequestInterface $request): ResponseInterface
+    {
+        if (Helper::isAjaxRequest($request)) {
+            throw new AJAXException(
+                WCF::getLanguage()->getDynamicVariable('wcf.user.reauthentication.explanation'),
+                AJAXException::SESSION_EXPIRED
+            );
+        }
+
+        return new RedirectResponse(
+            LinkHandler::getInstance()->getControllerLink(
+                ReauthenticationForm::class,
+                [
+                    'url' => (string)$request->getUri()
+                ]
+            )
+        );
+    }
+}
index f5fc42760ef29cc361a677c2373f87cbca068a44..3fb27f0be689aa50c106c4514633ee304da3bdcb 100644 (file)
@@ -138,87 +138,17 @@ class WCFACP extends WCF
 
                 exit;
             }
-        } elseif (
-            empty($pathInfo)
-            || !\preg_match('~^/?(login|(full-)?logout|multifactor-authentication|reauthentication)/~i', $pathInfo)
-        ) {
-            $isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
-
-            if (WCF::getUser()->userID == 0) {
-                // work-around for AJAX-requests within ACP
-                if ($isAjax) {
-                    throw new AJAXException(
-                        WCF::getLanguage()->getDynamicVariable('wcf.ajax.error.sessionExpired'),
-                        AJAXException::SESSION_EXPIRED,
-                        ''
-                    );
-                }
-
-                // build redirect path
-                $application = ApplicationHandler::getInstance()->getActiveApplication();
-                if ($application === null) {
-                    throw new SystemException("You have aborted the installation, therefore this installation is unusable. You are required to reinstall the software.");
-                }
-
-                HeaderUtil::redirect(
-                    LinkHandler::getInstance()->getLink('Login', [
-                        'url' => RouteHandler::getProtocol() . $_SERVER['HTTP_HOST'] . WCF::getSession()->requestURI,
-                    ])
-                );
-
-                exit;
-            } else {
-                try {
-                    WCF::getSession()->checkPermissions(['admin.general.canUseAcp']);
-                } catch (PermissionDeniedException $e) {
-                    self::getTPL()->assign([
-                        '__isLogin' => true,
-                    ]);
-
-                    if ($isAjax) {
-                        throw new AJAXException(
-                            self::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'),
-                            AJAXException::INSUFFICIENT_PERMISSIONS,
-                            $e->getTraceAsString()
-                        );
-                    } else {
-                        throw new NamedUserException(
-                            self::getLanguage()->getDynamicVariable('wcf.user.username.error.acpNotAuthorized')
-                        );
-                    }
-                }
-
-                if (WCF::getSession()->needsReauthentication()) {
-                    if ($isAjax) {
-                        throw new AJAXException(
-                            self::getLanguage()->getDynamicVariable('wcf.user.reauthentication.explanation'),
-                            AJAXException::SESSION_EXPIRED
-                        );
-                    }
-
-                    HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Reauthentication', [
-                        'url' => RouteHandler::getProtocol() . $_SERVER['HTTP_HOST'] . WCF::getSession()->requestURI,
-                    ]));
-
-                    exit;
-                }
-
-                // The autoloader is not available during the definition of `WCFACP`,
-                // thus we are unable to use the trait directly.
-                //
-                // Workaround this issue by using an anonymous class.
-                (new class {
-                    use TMultifactorRequirementEnforcer {
-                        enforceMultifactorAuthentication as public enforce;
-                    }
-                })->enforce();
-
-                // force debug mode if in ACP and authenticated
-                self::$overrideDebugMode = true;
-            }
         }
     }
 
+    /**
+     * @since 6.0
+     */
+    public static function overrideDebugMode()
+    {
+        self::$overrideDebugMode = true;
+    }
+
     /**
      * @inheritDoc
      */
index e29ade2f5a0d3241f41b96e43c906a3b50c3b035..17666388ea73e113a4c4229cc5c385849981a81d 100644 (file)
@@ -15,6 +15,7 @@ use wcf\http\middleware\CheckForExpiredAppEvaluation;
 use wcf\http\middleware\CheckForOfflineMode;
 use wcf\http\middleware\CheckSystemEnvironment;
 use wcf\http\middleware\CheckUserBan;
+use wcf\http\middleware\EnforceAcpAuthentication;
 use wcf\http\middleware\EnforceCacheControlPrivate;
 use wcf\http\middleware\EnforceFrameOptions;
 use wcf\http\Pipeline;
@@ -97,6 +98,7 @@ final class RequestHandler extends SingletonFactory
                     new EnforceFrameOptions(),
                     new CheckSystemEnvironment(),
                     new CheckUserBan(),
+                    new EnforceAcpAuthentication(),
                     new CheckForEnterpriseNonOwnerAccess(),
                     new CheckForExpiredAppEvaluation(),
                     new CheckForOfflineMode(),