Skip to content

Commit c52ec46

Browse files
committed
Move access token handling to a middleware
1 parent bef9399 commit c52ec46

File tree

5 files changed

+123
-1
lines changed

5 files changed

+123
-1
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace wcf\http\attribute;
4+
5+
/**
6+
* Allows the user to be authed for the current request via an access-token.
7+
* A missing token will be ignored, an invalid token results in a throw of a IllegalLinkException.
8+
*
9+
* @author Marcel Werk
10+
* @copyright 2001-2024 WoltLab GmbH
11+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
12+
* @since 6.1
13+
*/
14+
#[\Attribute(\Attribute::TARGET_CLASS)]
15+
final class AllowAccessToken {}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace wcf\http\middleware;
4+
5+
use Psr\Http\Message\ResponseInterface;
6+
use Psr\Http\Message\ServerRequestInterface;
7+
use Psr\Http\Server\MiddlewareInterface;
8+
use Psr\Http\Server\RequestHandlerInterface;
9+
use wcf\data\user\User;
10+
use wcf\http\attribute\AllowAccessToken;
11+
use wcf\http\error\NotFoundHandler;
12+
use wcf\system\request\RequestHandler;
13+
use wcf\system\session\SessionHandler;
14+
use wcf\system\WCF;
15+
16+
/**
17+
* Handles a given access-token, that allow the user to be authed for the current request.
18+
* A missing token will be ignored, an invalid token results in a throw of a IllegalLinkException.
19+
*
20+
* @author Marcel Werk
21+
* @copyright 2001-2024 WoltLab GmbH
22+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
23+
* @since 6.1
24+
*/
25+
final class HandleAccessToken implements MiddlewareInterface
26+
{
27+
/**
28+
* @inheritDoc
29+
*/
30+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
31+
{
32+
if (!$this->handleAccessToken($request->getQueryParams()['at'] ?? '')) {
33+
(new NotFoundHandler())->handle($request);
34+
}
35+
36+
return $handler->handle($request);
37+
}
38+
39+
private function handleAccessToken(string $accessToken): bool
40+
{
41+
if (!$accessToken) {
42+
return true;
43+
}
44+
45+
$activeRequest = RequestHandler::getInstance()->getActiveRequest();
46+
if (!$activeRequest) {
47+
return true;
48+
}
49+
50+
$reflectionClass = new \ReflectionClass($activeRequest->getClassName());
51+
if (!$this->hasAttribute($reflectionClass)) {
52+
return true;
53+
}
54+
55+
return $this->checkAccessToken($accessToken);
56+
}
57+
58+
private function hasAttribute(\ReflectionClass $class): bool
59+
{
60+
if ($class->getAttributes(AllowAccessToken::class) !== []) {
61+
return true;
62+
}
63+
64+
$parentClass = $class->getParentClass();
65+
if ($parentClass === false) {
66+
return false;
67+
}
68+
69+
return $this->hasAttribute($parentClass);
70+
}
71+
72+
private function checkAccessToken(string $accessToken): bool
73+
{
74+
if (!\preg_match('~^(?P<userID>\d{1,10})-(?P<token>[a-f0-9]{40})$~', $accessToken, $matches)) {
75+
return false;
76+
}
77+
78+
$userID = $matches['userID'];
79+
$token = $matches['token'];
80+
81+
if (WCF::getUser()->userID) {
82+
if ($userID == WCF::getUser()->userID && \hash_equals(WCF::getUser()->accessToken, $token)) {
83+
// Everything is fine, but the user is already logged in.
84+
return true;
85+
}
86+
} else {
87+
$user = new User($userID);
88+
if (
89+
$user->userID && $user->accessToken && \hash_equals(
90+
$user->accessToken,
91+
$token
92+
)
93+
) {
94+
// Token is valid so we log in the user for the current request.
95+
SessionHandler::getInstance()->changeUser($user, true);
96+
return true;
97+
}
98+
}
99+
100+
return false;
101+
}
102+
}

wcfsetup/install/files/lib/page/AbstractAuthedPage.class.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* @author Tim Duesterhus
1616
* @copyright 2001-2020 WoltLab GmbH
1717
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
18+
* @deprecated 6.1 Use `AllowAccessToken` instead.
1819
*/
1920
abstract class AbstractAuthedPage extends AbstractPage
2021
{

wcfsetup/install/files/lib/page/AbstractRssFeedPage.class.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace wcf\page;
44

5+
use wcf\http\attribute\AllowAccessToken;
56
use wcf\system\rssFeed\RssFeed;
67
use wcf\system\rssFeed\RssFeedChannel;
78
use wcf\system\WCF;
@@ -15,7 +16,8 @@
1516
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
1617
* @since 6.1
1718
*/
18-
abstract class AbstractRssFeedPage extends AbstractAuthedPage
19+
#[AllowAccessToken]
20+
abstract class AbstractRssFeedPage extends AbstractPage
1921
{
2022
/**
2123
* @inheritDoc

wcfsetup/install/files/lib/system/request/RequestHandler.class.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use wcf\http\middleware\EnforceCacheControlPrivate;
2929
use wcf\http\middleware\EnforceFrameOptions;
3030
use wcf\http\middleware\EnforceNoCacheForTemporaryRedirects;
31+
use wcf\http\middleware\HandleAccessToken;
3132
use wcf\http\middleware\HandleExceptions;
3233
use wcf\http\middleware\HandleStartupErrors;
3334
use wcf\http\middleware\HandleValinorMappingErrors;
@@ -141,6 +142,7 @@ public function handle(string $application = 'wcf', bool $isACPRequest = false):
141142
new CheckHttpMethod(),
142143
new Xsrf(),
143144
new CheckSystemEnvironment(),
145+
new HandleAccessToken(),
144146
new CheckUserBan(),
145147
new EnforceAcpAuthentication(),
146148
new CheckForEnterpriseNonOwnerAccess(),

0 commit comments

Comments
 (0)