From 9e4f33e5c56df3b44fc467809fb8d934a9fad6d7 Mon Sep 17 00:00:00 2001 From: lukinovec Date: Mon, 8 Jan 2024 00:29:01 +0100 Subject: [PATCH] Identify tenants by the "Origin" header (#21) * Add origin ID MW * Test origin ID MW * Test origin ID MW with early identification * Fix code style (php-cs-fixer) * Fix PHPStan errors * Add getDomain() to domain ID MW, simplify origin ID MW * Fix code style (php-cs-fixer) * Rename InitializeTenancyByOrigin to InitializeTenancyByOriginHeader * Add onFail test * Stop throwing the exception in getDomain() * FIx merge * Improve origin identification test file * Clean up test --------- Co-authored-by: PHP CS Fixer --- assets/config.php | 1 + src/Middleware/InitializeTenancyByDomain.php | 11 +++- .../InitializeTenancyByDomainOrSubdomain.php | 2 +- .../InitializeTenancyByOriginHeader.php | 15 +++++ .../InitializeTenancyBySubdomain.php | 2 +- tests/EarlyIdentificationTest.php | 41 ++++++++++++++ tests/OriginHeaderIdentificationTest.php | 56 +++++++++++++++++++ 7 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 src/Middleware/InitializeTenancyByOriginHeader.php create mode 100644 tests/OriginHeaderIdentificationTest.php diff --git a/assets/config.php b/assets/config.php index a7374a34..37aa921f 100644 --- a/assets/config.php +++ b/assets/config.php @@ -60,6 +60,7 @@ return [ Middleware\InitializeTenancyByDomainOrSubdomain::class, Middleware\InitializeTenancyByPath::class, Middleware\InitializeTenancyByRequestData::class, + Middleware\InitializeTenancyByOriginHeader::class, ], /** diff --git a/src/Middleware/InitializeTenancyByDomain.php b/src/Middleware/InitializeTenancyByDomain.php index bb757c19..35cbfa9a 100644 --- a/src/Middleware/InitializeTenancyByDomain.php +++ b/src/Middleware/InitializeTenancyByDomain.php @@ -31,10 +31,12 @@ class InitializeTenancyByDomain extends IdentificationMiddleware implements Usab return $next($request); } + $domain = $this->getDomain($request); + return $this->initializeTenancy( $request, $next, - $request->getHost() + $domain ); } @@ -44,6 +46,11 @@ class InitializeTenancyByDomain extends IdentificationMiddleware implements Usab */ public function requestHasTenant(Request $request): bool { - return ! in_array($request->host(), config('tenancy.central_domains')); + return ! in_array($this->getDomain($request), config('tenancy.central_domains')); + } + + public function getDomain(Request $request): string + { + return $request->getHost(); } } diff --git a/src/Middleware/InitializeTenancyByDomainOrSubdomain.php b/src/Middleware/InitializeTenancyByDomainOrSubdomain.php index 7917472c..32159aef 100644 --- a/src/Middleware/InitializeTenancyByDomainOrSubdomain.php +++ b/src/Middleware/InitializeTenancyByDomainOrSubdomain.php @@ -22,7 +22,7 @@ class InitializeTenancyByDomainOrSubdomain extends InitializeTenancyBySubdomain return $next($request); } - $domain = $request->getHost(); + $domain = $this->getDomain($request); if ($this->isSubdomain($domain)) { $domain = $this->makeSubdomain($domain); diff --git a/src/Middleware/InitializeTenancyByOriginHeader.php b/src/Middleware/InitializeTenancyByOriginHeader.php new file mode 100644 index 00000000..4d85dc5c --- /dev/null +++ b/src/Middleware/InitializeTenancyByOriginHeader.php @@ -0,0 +1,15 @@ +header('Origin', ''); + } +} diff --git a/src/Middleware/InitializeTenancyBySubdomain.php b/src/Middleware/InitializeTenancyBySubdomain.php index 9429349a..8da1b1bc 100644 --- a/src/Middleware/InitializeTenancyBySubdomain.php +++ b/src/Middleware/InitializeTenancyBySubdomain.php @@ -35,7 +35,7 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain return $next($request); } - $subdomain = $this->makeSubdomain($request->getHost()); + $subdomain = $this->makeSubdomain($this->getDomain($request)); if (is_object($subdomain) && $subdomain instanceof Exception) { $onFail = static::$onFail ?? function ($e) { diff --git a/tests/EarlyIdentificationTest.php b/tests/EarlyIdentificationTest.php index 532b6995..c0e6b577 100644 --- a/tests/EarlyIdentificationTest.php +++ b/tests/EarlyIdentificationTest.php @@ -19,6 +19,7 @@ use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData; use Stancl\Tenancy\Tests\Etc\EarlyIdentification\Models\Post; use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains; use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain; +use Stancl\Tenancy\Middleware\InitializeTenancyByOriginHeader; use Stancl\Tenancy\Tests\Etc\EarlyIdentification\ControllerWithMiddleware; use Stancl\Tenancy\Tests\Etc\EarlyIdentification\ControllerWithRouteMiddleware; @@ -175,6 +176,46 @@ test('early identification works with request data identification', function (st 'default to tenant routes' => RouteMode::TENANT, 'default to central routes' => RouteMode::CENTRAL, ]); +test('early identification works with origin identification', function (bool $useKernelIdentification, RouteMode $defaultRouteMode) { + $identificationMiddleware = InitializeTenancyByOriginHeader::class; + + if ($useKernelIdentification) { + $controller = ControllerWithMiddleware::class; + app(Kernel::class)->pushMiddleware($identificationMiddleware); + } else { + $controller = ControllerWithRouteMiddleware::class; + RouteFacade::middlewareGroup('tenant', [$identificationMiddleware]); + } + + config(['tenancy.default_route_mode' => $defaultRouteMode]); + + $tenantRouteMiddleware = 'tenant'; + + // If defaulting to tenant routes + // With kernel identification, we make the tenant route have no MW + // And with route-level identification, we make the route have only the identification middleware + if ($defaultRouteMode === RouteMode::TENANT) { + $tenantRouteMiddleware = $useKernelIdentification ? null : $identificationMiddleware; + } + + RouteFacade::post('/tenant-route', [$controller, 'index'])->middleware($tenantRouteMiddleware); + + $tenant = Tenant::create(); + + $tenant->domains()->create(['domain' => 'foo']); + + $tenantKey = $tenant->getTenantKey(); + + $response = pest()->post('/tenant-route', headers: ['Origin' => 'foo.localhost']); + + $response->assertOk()->assertSee('token:' . $tenantKey); +})->with([ + 'route-level identification' => false, + 'kernel identification' => true, +])->with([ + 'default to tenant routes' => RouteMode::TENANT, + 'default to central routes' => RouteMode::CENTRAL, +]); test('early identification works with domain identification', function (string $middleware, string $domain, bool $useKernelIdentification, RouteMode $defaultRouteMode) { config(['tenancy.default_route_mode' => $defaultRouteMode]); diff --git a/tests/OriginHeaderIdentificationTest.php b/tests/OriginHeaderIdentificationTest.php new file mode 100644 index 00000000..dd5b683b --- /dev/null +++ b/tests/OriginHeaderIdentificationTest.php @@ -0,0 +1,56 @@ + [ + 'localhost', + ], + ]); + + Route::post('/home', function () { + return response(tenant('id')); + })->middleware([InitializeTenancyByOriginHeader::class])->name('home'); +}); + +afterEach(function () { + InitializeTenancyByOriginHeader::$onFail = null; +}); + +test('origin identification works', function () { + $tenant = Tenant::create(); + + $tenant->domains()->create([ + 'domain' => 'foo', + ]); + + pest() + ->withHeader('Origin', 'foo.localhost') + ->post('home') + ->assertSee($tenant->id); +}); + +test('tenant routes are not accessible on central domains while using origin identification', function () { + pest() + ->withHeader('Origin', 'localhost') + ->post('home') + ->assertStatus(500); +}); + +test('onfail logic can be customized', function() { + InitializeTenancyByOriginHeader::$onFail = function () { + return response('onFail message'); + }; + + pest() + ->withHeader('Origin', 'bar.localhost') // 'bar'/'bar.localhost' is not an existing tenant domain + ->post('home') + ->assertSee('onFail message'); +});