From f328fc9c082853a0a7c8d286b82fafe769e14777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Sun, 10 May 2020 19:30:01 +0200 Subject: [PATCH] Combined subdomain/domain identification --- .../InitializeTenancyByDomainOrSubdomain.php | 32 ++++++++ .../InitializeTenancyBySubdomain.php | 9 ++- ...edDomainAndSubdomainIdentificationTest.php | 78 +++++++++++++++++++ tests/v3/SubdomainTest.php | 26 +++++++ 4 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 src/Middleware/InitializeTenancyByDomainOrSubdomain.php create mode 100644 tests/v3/CombinedDomainAndSubdomainIdentificationTest.php diff --git a/src/Middleware/InitializeTenancyByDomainOrSubdomain.php b/src/Middleware/InitializeTenancyByDomainOrSubdomain.php new file mode 100644 index 00000000..94217bba --- /dev/null +++ b/src/Middleware/InitializeTenancyByDomainOrSubdomain.php @@ -0,0 +1,32 @@ +isSubdomain($request->getHost())) { + return app(InitializeTenancyBySubdomain::class)->handle($request, $next); + } else { + return app(InitializeTenancyByDomain::class)->handle($request, $next); + } + } + + protected function isSubdomain(string $hostname): bool + { + return Str::endsWith($hostname, config('tenancy.central_domains')); + } +} diff --git a/src/Middleware/InitializeTenancyBySubdomain.php b/src/Middleware/InitializeTenancyBySubdomain.php index 2bad3c50..d2ca2a36 100644 --- a/src/Middleware/InitializeTenancyBySubdomain.php +++ b/src/Middleware/InitializeTenancyBySubdomain.php @@ -7,6 +7,7 @@ namespace Stancl\Tenancy\Middleware; use Closure; use Illuminate\Http\Response; use Stancl\Tenancy\Exceptions\NotASubdomainException; +use Illuminate\Support\Str; class InitializeTenancyBySubdomain extends InitializeTenancyByDomain { @@ -51,7 +52,10 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain $parts = explode('.', $hostname); // If we're on localhost or an IP address, then we're not visiting a subdomain. - if (in_array(count($parts), [1, 4])) { + $notADomain = in_array(count($parts), [1, 4]); + $thirdPartyDomain = ! Str::endsWith($hostname, config('tenancy.central_domains'));; + + if ($notADomain || $thirdPartyDomain) { $handle = static::$onInvalidSubdomain ?? function ($e) { throw $e; }; @@ -59,9 +63,6 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain return $handle(new NotASubdomainException($hostname)); } - // todo should we verify that the subdomain belongs to one of our central domains? - // if yes, then write a test for it. - return $parts[static::$subdomainIndex]; } } diff --git a/tests/v3/CombinedDomainAndSubdomainIdentificationTest.php b/tests/v3/CombinedDomainAndSubdomainIdentificationTest.php new file mode 100644 index 00000000..68e843a4 --- /dev/null +++ b/tests/v3/CombinedDomainAndSubdomainIdentificationTest.php @@ -0,0 +1,78 @@ + InitializeTenancyByDomainOrSubdomain::class, + ], function () { + Route::get('/foo/{a}/{b}', function ($a, $b) { + return "$a + $b"; + }); + }); + + config(['tenancy.tenant_model' => Tenant::class]); + } + + /** @test */ + public function tenant_can_be_identified_by_subdomain() + { + config(['tenancy.central_domains' => ['localhost']]); + + $tenant = Tenant::create([ + 'id' => 'acme', + ]); + + $tenant->domains()->create([ + 'domain' => 'foo', + ]); + + $this->assertFalse(tenancy()->initialized); + + $this + ->get('http://foo.localhost/foo/abc/xyz') + ->assertSee('abc + xyz'); + + $this->assertTrue(tenancy()->initialized); + $this->assertSame('acme', tenant('id')); + } + + /** @test */ + public function tenant_can_be_identified_by_domain() + { + config(['tenancy.central_domains' => []]); + + $tenant = Tenant::create([ + 'id' => 'acme', + ]); + + $tenant->domains()->create([ + 'domain' => 'foobar.localhost', + ]); + + $this->assertFalse(tenancy()->initialized); + + $this + ->get('http://foobar.localhost/foo/abc/xyz') + ->assertSee('abc + xyz'); + + $this->assertTrue(tenancy()->initialized); + $this->assertSame('acme', tenant('id')); + } +} + +class Tenant extends Models\Tenant +{ + use HasDomains; +} diff --git a/tests/v3/SubdomainTest.php b/tests/v3/SubdomainTest.php index 38b3054e..ba483a66 100644 --- a/tests/v3/SubdomainTest.php +++ b/tests/v3/SubdomainTest.php @@ -15,6 +15,9 @@ class SubdomainTest extends TestCase { parent::setUp(); + // Global state cleanup after some tests + InitializeTenancyBySubdomain::$onInvalidSubdomain = null; + Route::group([ 'middleware' => InitializeTenancyBySubdomain::class, ], function () { @@ -93,6 +96,29 @@ class SubdomainTest extends TestCase ->get('http://127.0.0.1/foo/abc/xyz') ->assertSee('foo custom invalid subdomain handler'); } + + /** @test */ + public function we_cant_use_a_subdomain_that_doesnt_belong_to_our_central_domains() + { + config(['tenancy.central_domains' => [ + '127.0.0.1', + // not 'localhost' + ]]); + + $tenant = Tenant::create([ + 'id' => 'acme', + ]); + + $tenant->domains()->create([ + 'domain' => 'foo', + ]); + + $this->expectException(NotASubdomainException::class); + + $this + ->withoutExceptionHandling() + ->get('http://foo.localhost/foo/abc/xyz'); + } } class Tenant extends Models\Tenant