Move access token handling to a middleware
authorMarcel Werk <burntime@woltlab.com>
Mon, 30 Dec 2024 14:35:03 +0000 (15:35 +0100)
committerMarcel Werk <burntime@woltlab.com>
Mon, 30 Dec 2024 14:35:03 +0000 (15:35 +0100)
wcfsetup/install/files/lib/http/attribute/AllowAccessToken.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/http/middleware/HandleAccessToken.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/page/AbstractAuthedPage.class.php
wcfsetup/install/files/lib/page/AbstractRssFeedPage.class.php
wcfsetup/install/files/lib/system/request/RequestHandler.class.php

diff --git a/wcfsetup/install/files/lib/http/attribute/AllowAccessToken.class.php b/wcfsetup/install/files/lib/http/attribute/AllowAccessToken.class.php
new file mode 100644 (file)
index 0000000..99d0a50
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+
+namespace wcf\http\attribute;
+
+/**
+ * Allows the user to be authed for the current request via an access-token.
+ * A missing token will be ignored, an invalid token results in a throw of a IllegalLinkException.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+final class AllowAccessToken {}
diff --git a/wcfsetup/install/files/lib/http/middleware/HandleAccessToken.class.php b/wcfsetup/install/files/lib/http/middleware/HandleAccessToken.class.php
new file mode 100644 (file)
index 0000000..fa657cd
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+
+namespace wcf\http\middleware;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use wcf\data\user\User;
+use wcf\http\attribute\AllowAccessToken;
+use wcf\http\error\NotFoundHandler;
+use wcf\system\request\RequestHandler;
+use wcf\system\session\SessionHandler;
+use wcf\system\WCF;
+
+/**
+ * Handles a given access-token, that allow the user to be authed for the current request.
+ * A missing token will be ignored, an invalid token results in a throw of a IllegalLinkException.
+ *
+ * @author      Marcel Werk
+ * @copyright   2001-2024 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since       6.1
+ */
+final class HandleAccessToken implements MiddlewareInterface
+{
+    /**
+     * @inheritDoc
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        if (!$this->handleAccessToken($request->getQueryParams()['at'] ?? '')) {
+            (new NotFoundHandler())->handle($request);
+        }
+
+        return $handler->handle($request);
+    }
+
+    private function handleAccessToken(string $accessToken): bool
+    {
+        if (!$accessToken) {
+            return true;
+        }
+
+        $activeRequest = RequestHandler::getInstance()->getActiveRequest();
+        if (!$activeRequest) {
+            return true;
+        }
+
+        $reflectionClass = new \ReflectionClass($activeRequest->getClassName());
+        if (!$this->hasAttribute($reflectionClass)) {
+            return true;
+        }
+
+        return $this->checkAccessToken($accessToken);
+    }
+
+    private function hasAttribute(\ReflectionClass $class): bool
+    {
+        if ($class->getAttributes(AllowAccessToken::class) !== []) {
+            return true;
+        }
+
+        $parentClass = $class->getParentClass();
+        if ($parentClass === false) {
+            return false;
+        }
+
+        return $this->hasAttribute($parentClass);
+    }
+
+    private function checkAccessToken(string $accessToken): bool
+    {
+        if (!\preg_match('~^(?P<userID>\d{1,10})-(?P<token>[a-f0-9]{40})$~', $accessToken, $matches)) {
+            return false;
+        }
+
+        $userID = $matches['userID'];
+        $token = $matches['token'];
+
+        if (WCF::getUser()->userID) {
+            if ($userID == WCF::getUser()->userID && \hash_equals(WCF::getUser()->accessToken, $token)) {
+                // Everything is fine, but the user is already logged in.
+                return true;
+            }
+        } else {
+            $user = new User($userID);
+            if (
+                $user->userID && $user->accessToken && \hash_equals(
+                    $user->accessToken,
+                    $token
+                )
+            ) {
+                // Token is valid so we log in the user for the current request.
+                SessionHandler::getInstance()->changeUser($user, true);
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
index 5106167198e87aa4c85443dc48dc1b0ce41d5f60..38b4131b14323acbc9312c3ebdbe338326de3896 100644 (file)
@@ -15,6 +15,7 @@ use wcf\system\WCF;
  * @author  Tim Duesterhus
  * @copyright   2001-2020 WoltLab GmbH
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @deprecated 6.1 Use `AllowAccessToken` instead.
  */
 abstract class AbstractAuthedPage extends AbstractPage
 {
index 95f70608e4f39e5ae300d1af125217c9ae848c41..125a2c911d57ef72bbf0329e1a4a686741a1579a 100644 (file)
@@ -2,6 +2,7 @@
 
 namespace wcf\page;
 
+use wcf\http\attribute\AllowAccessToken;
 use wcf\system\rssFeed\RssFeed;
 use wcf\system\rssFeed\RssFeedChannel;
 use wcf\system\WCF;
@@ -15,7 +16,8 @@ use wcf\util\ArrayUtil;
  * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @since       6.1
  */
-abstract class AbstractRssFeedPage extends AbstractAuthedPage
+#[AllowAccessToken]
+abstract class AbstractRssFeedPage extends AbstractPage
 {
     /**
      * @inheritDoc
index b3716b0ee7d2983f83e2b6ba4cacc0beb8205a1a..e6ba50e1d59d3883ebb2c159ae7dde99539cbf2a 100644 (file)
@@ -28,6 +28,7 @@ use wcf\http\middleware\EnforceAcpAuthentication;
 use wcf\http\middleware\EnforceCacheControlPrivate;
 use wcf\http\middleware\EnforceFrameOptions;
 use wcf\http\middleware\EnforceNoCacheForTemporaryRedirects;
+use wcf\http\middleware\HandleAccessToken;
 use wcf\http\middleware\HandleExceptions;
 use wcf\http\middleware\HandleStartupErrors;
 use wcf\http\middleware\HandleValinorMappingErrors;
@@ -141,6 +142,7 @@ final class RequestHandler extends SingletonFactory
                     new CheckHttpMethod(),
                     new Xsrf(),
                     new CheckSystemEnvironment(),
+                    new HandleAccessToken(),
                     new CheckUserBan(),
                     new EnforceAcpAuthentication(),
                     new CheckForEnterpriseNonOwnerAccess(),