From cb74a5756a81d0b961756583ab348fbd2eccc894 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 6 Apr 2026 10:20:18 +0530 Subject: [PATCH 1/2] Remove request and response static state --- app/controllers/general.php | 4 ++-- src/Appwrite/Utopia/Request.php | 18 ++++++++--------- src/Appwrite/Utopia/Response.php | 31 +++++++++++++++++++++++++++--- tests/unit/Utopia/RequestTest.php | 15 +++++++++++++++ tests/unit/Utopia/ResponseTest.php | 23 ++++++++++++++++++++++ 5 files changed, 77 insertions(+), 14 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 3bf5f027f2..dcc5764bdd 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -868,7 +868,7 @@ Http::init() * Request format */ $route = $utopia->getRoute(); - Request::setRoute($route); + $request->setRoute($route); if ($route === null) { $response->setStatusCode(404); @@ -1019,7 +1019,7 @@ Http::init() return; } $route = $request->getRoute(); - if ($route->getLabel('origin', false) === '*') { + if ($route?->getLabel('origin', false) === '*') { return; } if (!$originValidator->isValid($origin)) { diff --git a/src/Appwrite/Utopia/Request.php b/src/Appwrite/Utopia/Request.php index 9428ff9d88..ed602ecdd5 100644 --- a/src/Appwrite/Utopia/Request.php +++ b/src/Appwrite/Utopia/Request.php @@ -17,7 +17,7 @@ class Request extends UtopiaRequest * @var array */ private array $filters = []; - private static ?Route $route = null; + private ?Route $route = null; public function __construct(SwooleRequest $request) { @@ -34,11 +34,11 @@ class Request extends UtopiaRequest { $parameters = parent::getParams(); - if (!$this->hasFilters() || !self::hasRoute()) { + if (!$this->hasFilters() || !$this->hasRoute()) { return $parameters; } - $methods = self::getRoute()->getLabel('sdk', null); + $methods = $this->getRoute()?->getLabel('sdk', null); if (empty($methods)) { return $parameters; @@ -131,9 +131,9 @@ class Request extends UtopiaRequest * * @return void */ - public static function setRoute(?Route $route): void + public function setRoute(?Route $route): void { - self::$route = $route; + $this->route = $route; } /** @@ -141,9 +141,9 @@ class Request extends UtopiaRequest * * @return Route|null */ - public static function getRoute(): ?Route + public function getRoute(): ?Route { - return self::$route; + return $this->route; } /** @@ -151,9 +151,9 @@ class Request extends UtopiaRequest * * @return bool */ - public static function hasRoute(): bool + public function hasRoute(): bool { - return self::$route !== null; + return $this->route !== null; } /** diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index e01dc58bf6..649b0562a5 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -8,6 +8,7 @@ use Appwrite\Utopia\Response\Filter; use Appwrite\Utopia\Response\Model; use Exception; use JsonException; +use Swoole\Coroutine; use Swoole\Http\Response as SwooleHTTPResponse; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; @@ -19,6 +20,8 @@ use Utopia\Http\Adapter\Swoole\Response as SwooleResponse; */ class Response extends SwooleResponse { + private const SHOW_SENSITIVE_CONTEXT_KEY = '__appwrite_response_show_sensitive'; + // General public const MODEL_NONE = 'none'; public const MODEL_ANY = 'any'; @@ -509,7 +512,7 @@ class Response extends SwooleResponse $isPrivilegedUser = $user->isPrivileged($roles); $isAppUser = $user->isApp($roles); - if ((!$isPrivilegedUser && !$isAppUser) && !self::$showSensitive) { + if ((!$isPrivilegedUser && !$isAppUser) && !self::isShowingSensitive()) { $data->setAttribute($key, ''); } } @@ -666,14 +669,36 @@ class Response extends SwooleResponse */ public static function showSensitive(callable $callback): array { + $previous = self::isShowingSensitive(); + try { - self::$showSensitive = true; + self::setShowSensitive(true); return $callback(); } finally { - self::$showSensitive = false; + self::setShowSensitive($previous); } } + private static function isShowingSensitive(): bool + { + if (Coroutine::getCid() !== -1) { + return (bool) (Coroutine::getContext()[self::SHOW_SENSITIVE_CONTEXT_KEY] ?? false); + } + + return self::$showSensitive; + } + + private static function setShowSensitive(bool $value): void + { + if (Coroutine::getCid() !== -1) { + Coroutine::getContext()[self::SHOW_SENSITIVE_CONTEXT_KEY] = $value; + + return; + } + + self::$showSensitive = $value; + } + private ?Authorization $authorization = null; private ?DBUser $user = null; diff --git a/tests/unit/Utopia/RequestTest.php b/tests/unit/Utopia/RequestTest.php index 78a3717c38..d5cd5d800a 100644 --- a/tests/unit/Utopia/RequestTest.php +++ b/tests/unit/Utopia/RequestTest.php @@ -147,6 +147,21 @@ class RequestTest extends TestCase $this->assertSame('unexpected', $params['extra']); } + public function testRouteIsScopedToRequestInstance(): void + { + $firstRequest = new Request(new SwooleRequest()); + $secondRequest = new Request(new SwooleRequest()); + + $firstRoute = new Route(Request::METHOD_GET, '/first'); + $secondRoute = new Route(Request::METHOD_GET, '/second'); + + $firstRequest->setRoute($firstRoute); + $secondRequest->setRoute($secondRoute); + + $this->assertSame($firstRoute, $firstRequest->getRoute()); + $this->assertSame($secondRoute, $secondRequest->getRoute()); + } + /** * Helper to attach a route with multiple SDK methods to the request. */ diff --git a/tests/unit/Utopia/ResponseTest.php b/tests/unit/Utopia/ResponseTest.php index 452119fafb..d5c3a079cd 100644 --- a/tests/unit/Utopia/ResponseTest.php +++ b/tests/unit/Utopia/ResponseTest.php @@ -5,6 +5,7 @@ namespace Tests\Unit\Utopia; use Appwrite\Utopia\Response; use Exception; use PHPUnit\Framework\TestCase; +use ReflectionMethod; use Swoole\Http\Response as SwooleResponse; use Tests\Unit\Utopia\Response\Filters\First; use Tests\Unit\Utopia\Response\Filters\Second; @@ -176,4 +177,26 @@ class ResponseTest extends TestCase $this->assertArrayHasKey('required', $single); $this->assertArrayNotHasKey('hidden', $singleFromArray); } + + public function testShowSensitiveRestoresPreviousState(): void + { + $isShowingSensitive = new ReflectionMethod(Response::class, 'isShowingSensitive'); + + $this->assertFalse($isShowingSensitive->invoke(null)); + + $payload = Response::showSensitive(function () use ($isShowingSensitive) { + return [ + 'outer' => $isShowingSensitive->invoke(null), + 'inner' => Response::showSensitive(fn () => [ + 'state' => $isShowingSensitive->invoke(null), + ]), + 'afterInner' => $isShowingSensitive->invoke(null), + ]; + }); + + $this->assertTrue($payload['outer']); + $this->assertTrue($payload['inner']['state']); + $this->assertTrue($payload['afterInner']); + $this->assertFalse($isShowingSensitive->invoke(null)); + } } From b8eb0810c2d94ac4061311fbbefead5f8729f66e Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Mon, 6 Apr 2026 10:24:32 +0530 Subject: [PATCH 2/2] Make response sensitive mode instance-scoped --- app/controllers/api/account.php | 8 +++---- src/Appwrite/Utopia/Response.php | 37 ++++++------------------------ tests/unit/Utopia/ResponseTest.php | 18 +++++++-------- 3 files changed, 20 insertions(+), 43 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index d576bbce44..fb968d3972 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3937,7 +3937,7 @@ Http::post('/v1/account/recovery') ->setParam('userId', $profile->getId()) ->setParam('tokenId', $recovery->getId()) ->setUser($profile) - ->setPayload(Response::showSensitive(fn () => $response->output($recovery, Response::MODEL_TOKEN)), sensitive: ['secret']); + ->setPayload($response->showSensitive(fn () => $response->output($recovery, Response::MODEL_TOKEN)), sensitive: ['secret']); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -4038,7 +4038,7 @@ Http::put('/v1/account/recovery') $queueForEvents ->setParam('userId', $profile->getId()) ->setParam('tokenId', $recoveryDocument->getId()) - ->setPayload(Response::showSensitive(fn () => $response->output($recoveryDocument, Response::MODEL_TOKEN)), sensitive: ['secret']); + ->setPayload($response->showSensitive(fn () => $response->output($recoveryDocument, Response::MODEL_TOKEN)), sensitive: ['secret']); $response->dynamic($recoveryDocument, Response::MODEL_TOKEN); }); @@ -4268,7 +4268,7 @@ Http::post('/v1/account/verifications/email') $queueForEvents ->setParam('userId', $user->getId()) ->setParam('tokenId', $verification->getId()) - ->setPayload(Response::showSensitive(fn () => $response->output($verification, Response::MODEL_TOKEN)), sensitive: ['secret']); + ->setPayload($response->showSensitive(fn () => $response->output($verification, Response::MODEL_TOKEN)), sensitive: ['secret']); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -4360,7 +4360,7 @@ Http::put('/v1/account/verifications/email') $queueForEvents ->setParam('userId', $userId) ->setParam('tokenId', $verification->getId()) - ->setPayload(Response::showSensitive(fn () => $response->output($verification, Response::MODEL_TOKEN)), sensitive: ['secret']); + ->setPayload($response->showSensitive(fn () => $response->output($verification, Response::MODEL_TOKEN)), sensitive: ['secret']); $response->dynamic($verification, Response::MODEL_TOKEN); }); diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 649b0562a5..9d0e8abefa 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -8,7 +8,6 @@ use Appwrite\Utopia\Response\Filter; use Appwrite\Utopia\Response\Model; use Exception; use JsonException; -use Swoole\Coroutine; use Swoole\Http\Response as SwooleHTTPResponse; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; @@ -20,8 +19,6 @@ use Utopia\Http\Adapter\Swoole\Response as SwooleResponse; */ class Response extends SwooleResponse { - private const SHOW_SENSITIVE_CONTEXT_KEY = '__appwrite_response_show_sensitive'; - // General public const MODEL_NONE = 'none'; public const MODEL_ANY = 'any'; @@ -302,7 +299,7 @@ class Response extends SwooleResponse /** * @var bool */ - protected static bool $showSensitive = false; + protected bool $showSensitive = false; /** * @var array @@ -512,7 +509,7 @@ class Response extends SwooleResponse $isPrivilegedUser = $user->isPrivileged($roles); $isAppUser = $user->isApp($roles); - if ((!$isPrivilegedUser && !$isAppUser) && !self::isShowingSensitive()) { + if ((!$isPrivilegedUser && !$isAppUser) && !$this->showSensitive) { $data->setAttribute($key, ''); } } @@ -662,43 +659,23 @@ class Response extends SwooleResponse } /** - * Static wrapper to show sensitive data in response + * Wrapper to show sensitive data in response * * @param callable(): array $callback The callback to show sensitive information for * @return array */ - public static function showSensitive(callable $callback): array + public function showSensitive(callable $callback): array { - $previous = self::isShowingSensitive(); + $previous = $this->showSensitive; try { - self::setShowSensitive(true); + $this->showSensitive = true; return $callback(); } finally { - self::setShowSensitive($previous); + $this->showSensitive = $previous; } } - private static function isShowingSensitive(): bool - { - if (Coroutine::getCid() !== -1) { - return (bool) (Coroutine::getContext()[self::SHOW_SENSITIVE_CONTEXT_KEY] ?? false); - } - - return self::$showSensitive; - } - - private static function setShowSensitive(bool $value): void - { - if (Coroutine::getCid() !== -1) { - Coroutine::getContext()[self::SHOW_SENSITIVE_CONTEXT_KEY] = $value; - - return; - } - - self::$showSensitive = $value; - } - private ?Authorization $authorization = null; private ?DBUser $user = null; diff --git a/tests/unit/Utopia/ResponseTest.php b/tests/unit/Utopia/ResponseTest.php index d5c3a079cd..be8cfdc216 100644 --- a/tests/unit/Utopia/ResponseTest.php +++ b/tests/unit/Utopia/ResponseTest.php @@ -5,7 +5,7 @@ namespace Tests\Unit\Utopia; use Appwrite\Utopia\Response; use Exception; use PHPUnit\Framework\TestCase; -use ReflectionMethod; +use ReflectionProperty; use Swoole\Http\Response as SwooleResponse; use Tests\Unit\Utopia\Response\Filters\First; use Tests\Unit\Utopia\Response\Filters\Second; @@ -180,23 +180,23 @@ class ResponseTest extends TestCase public function testShowSensitiveRestoresPreviousState(): void { - $isShowingSensitive = new ReflectionMethod(Response::class, 'isShowingSensitive'); + $isShowingSensitive = new ReflectionProperty(Response::class, 'showSensitive'); - $this->assertFalse($isShowingSensitive->invoke(null)); + $this->assertFalse($isShowingSensitive->getValue($this->response)); - $payload = Response::showSensitive(function () use ($isShowingSensitive) { + $payload = $this->response->showSensitive(function () use ($isShowingSensitive) { return [ - 'outer' => $isShowingSensitive->invoke(null), - 'inner' => Response::showSensitive(fn () => [ - 'state' => $isShowingSensitive->invoke(null), + 'outer' => $isShowingSensitive->getValue($this->response), + 'inner' => $this->response->showSensitive(fn () => [ + 'state' => $isShowingSensitive->getValue($this->response), ]), - 'afterInner' => $isShowingSensitive->invoke(null), + 'afterInner' => $isShowingSensitive->getValue($this->response), ]; }); $this->assertTrue($payload['outer']); $this->assertTrue($payload['inner']['state']); $this->assertTrue($payload['afterInner']); - $this->assertFalse($isShowingSensitive->invoke(null)); + $this->assertFalse($isShowingSensitive->getValue($this->response)); } }