From 15d12e22c729989f184fa2fbd0afc0550e336a3f Mon Sep 17 00:00:00 2001 From: lukinovec Date: Tue, 6 Aug 2024 02:19:11 +0200 Subject: [PATCH] Fix cookie identification (#56) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * route cloning: Trim '/' from original route prefixes * Decrypt cookie if encrypted in request data ID MW * Fix code style (php-cs-fixer) * Fix PHPStan error [ci skip] * Revert "route cloning: Trim '/' from original route prefixes" This reverts commit 3dc97eba1b3b91b3446bc16fd107385fe835621e. * Fix code style (php-cs-fixer) * add a setting for requiring cookie encryption * Fix code style (php-cs-fixer) --------- Co-authored-by: PHP CS Fixer Co-authored-by: Samuel Ć tancl --- .../InitializeTenancyByRequestData.php | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/Middleware/InitializeTenancyByRequestData.php b/src/Middleware/InitializeTenancyByRequestData.php index f36cadfd..78f5db0d 100644 --- a/src/Middleware/InitializeTenancyByRequestData.php +++ b/src/Middleware/InitializeTenancyByRequestData.php @@ -5,7 +5,9 @@ declare(strict_types=1); namespace Stancl\Tenancy\Middleware; use Closure; +use Illuminate\Cookie\CookieValuePrefix; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Crypt; use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException; use Stancl\Tenancy\Overrides\TenancyUrlGenerator; @@ -21,6 +23,8 @@ class InitializeTenancyByRequestData extends IdentificationMiddleware public static string $queryParameter = 'tenant'; public static ?Closure $onFail = null; + public static bool $requireCookieEncryption = false; + public function __construct( protected Tenancy $tenancy, protected RequestDataTenantResolver $resolver, @@ -38,7 +42,11 @@ class InitializeTenancyByRequestData extends IdentificationMiddleware TenancyUrlGenerator::$prefixRouteNames = false; if ($request->method() !== 'OPTIONS') { - return $this->initializeTenancy($request, $next, $this->getPayload($request)); + return $this->initializeTenancy( + $request, + $next, + $this->getPayload($request) + ); } return $next($request); @@ -48,10 +56,17 @@ class InitializeTenancyByRequestData extends IdentificationMiddleware { if (static::$header && $request->hasHeader(static::$header)) { $payload = $request->header(static::$header); - } elseif (static::$queryParameter && $request->has(static::$queryParameter)) { + } elseif ( + static::$queryParameter && + $request->has(static::$queryParameter) + ) { $payload = $request->get(static::$queryParameter); } elseif (static::$cookie && $request->hasCookie(static::$cookie)) { $payload = $request->cookie(static::$cookie); + + if ($payload) { + $payload = $this->getTenantFromCookie($payload); + } } else { $payload = null; } @@ -70,4 +85,36 @@ class InitializeTenancyByRequestData extends IdentificationMiddleware { return (bool) $this->getPayload($request); } + + protected function getTenantFromCookie(string $cookie): string|null + { + // If the cookie looks like it's encrypted, we try decrypting it + if (str_starts_with($cookie, 'eyJpdiI')) { + try { + $json = base64_decode($cookie); + $data = json_decode($json, true); + + if ( + is_array($data) && + isset($data['iv']) && + isset($data['value']) && + isset($data['mac']) + ) { + // We can confidently assert that the cookie is encrypted. If this call were to fail, this method would just + // return null and the cookie payload would get skipped. + $cookie = CookieValuePrefix::validate( + static::$cookie, + Crypt::decryptString($cookie), + Crypt::getAllKeys() + ); + } + } catch (\Throwable) { + // In case of any exceptions, we just use the original cookie value. + } + } elseif (static::$requireCookieEncryption) { + return null; + } + + return $cookie; + } }