From c52ec46143b7f1962cbb8d2ada1c09ca8f231644 Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Mon, 30 Dec 2024 15:35:03 +0100 Subject: [PATCH] Move access token handling to a middleware --- .../http/attribute/AllowAccessToken.class.php | 15 +++ .../middleware/HandleAccessToken.class.php | 102 ++++++++++++++++++ .../lib/page/AbstractAuthedPage.class.php | 1 + .../lib/page/AbstractRssFeedPage.class.php | 4 +- .../system/request/RequestHandler.class.php | 2 + 5 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 wcfsetup/install/files/lib/http/attribute/AllowAccessToken.class.php create mode 100644 wcfsetup/install/files/lib/http/middleware/HandleAccessToken.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 index 0000000000..99d0a504e6 --- /dev/null +++ b/wcfsetup/install/files/lib/http/attribute/AllowAccessToken.class.php @@ -0,0 +1,15 @@ + + * @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 index 0000000000..fa657cd245 --- /dev/null +++ b/wcfsetup/install/files/lib/http/middleware/HandleAccessToken.class.php @@ -0,0 +1,102 @@ + + * @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\d{1,10})-(?P[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; + } +} diff --git a/wcfsetup/install/files/lib/page/AbstractAuthedPage.class.php b/wcfsetup/install/files/lib/page/AbstractAuthedPage.class.php index 5106167198..38b4131b14 100644 --- a/wcfsetup/install/files/lib/page/AbstractAuthedPage.class.php +++ b/wcfsetup/install/files/lib/page/AbstractAuthedPage.class.php @@ -15,6 +15,7 @@ use wcf\system\WCF; * @author Tim Duesterhus * @copyright 2001-2020 WoltLab GmbH * @license GNU Lesser General Public License + * @deprecated 6.1 Use `AllowAccessToken` instead. */ abstract class AbstractAuthedPage extends AbstractPage { diff --git a/wcfsetup/install/files/lib/page/AbstractRssFeedPage.class.php b/wcfsetup/install/files/lib/page/AbstractRssFeedPage.class.php index 95f70608e4..125a2c911d 100644 --- a/wcfsetup/install/files/lib/page/AbstractRssFeedPage.class.php +++ b/wcfsetup/install/files/lib/page/AbstractRssFeedPage.class.php @@ -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 * @since 6.1 */ -abstract class AbstractRssFeedPage extends AbstractAuthedPage +#[AllowAccessToken] +abstract class AbstractRssFeedPage extends AbstractPage { /** * @inheritDoc diff --git a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php index b3716b0ee7..e6ba50e1d5 100644 --- a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php +++ b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php @@ -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(), -- 2.20.1