mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 09:34:04 +00:00
Early identification support (#1)
* wip * Improve tests * rename class * wip * improve tests * introduce early identification middlewares * Update PreventAccessFromCentralDomains.php * method rename * method rename * Update UniversalRouteTest.php * Update UniversalRouteTest.php * Update EarlyIdentificationTest.php * remove early classes and add check in existing classes * MWs improvements * Update UniversalRouteTest.php * Fix code style (php-cs-fixer) * trigger ci * fix failing test after merge * add test for universal route in early identification * Update InitializeTenancyByDomain.php * Update UniversalRouteTest.php * remove `routeHasMiddleware` method from MW and use the UniversalRoutes class method * helper dockblock * add test * handle universal routes in early identification * remove UniversalRoute class because we are not using it anymore * rename class from PreventAccessFromCentralDomains to PreventAccessFromUnwantedDomains * improvements after self review * Update PreventAccessFromUnwantedDomains.php * remove inline class namespaces * remove DomainTenant class and use the Tenant class * update comment * removed custom expection and add method * Update tests/EarlyIdentificationTest.php Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com> * use ltrim and simplify the comment * remove comments and typhint * dataset and keys rename * rename $route parameter * removed helper functions * fix style * Update InitializeTenancyByPath.php * Update tests/EarlyIdentificationTest.php * code style * improve subdomain test * use TenancyInitialized event and remove DomainTenant alias * remove comment and move expectException below * code style * use TenancyInitialized event * improve test * improve datasets * Initialized -> Initializing * Update InitializeTenancyByPath.php * remove todo * Fix code style (php-cs-fixer) * refactor helper method * Update UniversalRouteTest.php * add note above test * remove after each hook * renamed universal_middleware to global_middleware * remove helper and improve url names * change check position * Revert "change check position" This reverts commit e4371d2f3aa8ad7ae5e5b7d15781b72a5f1be03c. * repositioned central check * add comment Co-authored-by: PHP CS Fixer <phpcsfixer@example.com> Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>
This commit is contained in:
parent
9520cbc811
commit
ff46bcfe20
18 changed files with 330 additions and 163 deletions
|
|
@ -153,7 +153,7 @@ class TenancyServiceProvider extends ServiceProvider
|
|||
protected function makeTenancyMiddlewareHighestPriority()
|
||||
{
|
||||
// PreventAccessFromCentralDomains has even higher priority than the identification middleware
|
||||
$tenancyMiddleware = array_merge([Middleware\PreventAccessFromCentralDomains::class], config('tenancy.identification.middleware'));
|
||||
$tenancyMiddleware = array_merge([Middleware\PreventAccessFromUnwantedDomains::class], config('tenancy.identification.middleware'));
|
||||
|
||||
foreach (array_reverse($tenancyMiddleware) as $middleware) {
|
||||
$this->app[\Illuminate\Contracts\Http\Kernel::class]->prependToMiddlewarePriority($middleware);
|
||||
|
|
|
|||
|
|
@ -281,7 +281,6 @@ return [
|
|||
'features' => [
|
||||
// Stancl\Tenancy\Features\UserImpersonation::class,
|
||||
// Stancl\Tenancy\Features\TelescopeTags::class,
|
||||
// Stancl\Tenancy\Features\UniversalRoutes::class,
|
||||
// Stancl\Tenancy\Features\TenantConfig::class, // https://tenancyforlaravel.com/docs/v3/features/tenant-config
|
||||
// Stancl\Tenancy\Features\CrossDomainRedirect::class, // https://tenancyforlaravel.com/docs/v3/features/cross-domain-redirect
|
||||
],
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
use Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains;
|
||||
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
@ -21,7 +21,7 @@ use Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains;
|
|||
Route::middleware([
|
||||
'web',
|
||||
InitializeTenancyByDomain::class,
|
||||
PreventAccessFromCentralDomains::class,
|
||||
PreventAccessFromUnwantedDomains::class,
|
||||
])->group(function () {
|
||||
Route::get('/', function () {
|
||||
return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Features;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Routing\Route;
|
||||
use Illuminate\Support\Facades\Route as Router;
|
||||
use Stancl\Tenancy\Contracts\Feature;
|
||||
use Stancl\Tenancy\Middleware;
|
||||
use Stancl\Tenancy\Tenancy;
|
||||
|
||||
class UniversalRoutes implements Feature
|
||||
{
|
||||
public static string $middlewareGroup = 'universal';
|
||||
|
||||
// todo docblock
|
||||
/** @var array<class-string<\Stancl\Tenancy\Middleware\IdentificationMiddleware>> */
|
||||
public static array $identificationMiddlewares = [
|
||||
Middleware\InitializeTenancyByDomain::class,
|
||||
Middleware\InitializeTenancyBySubdomain::class,
|
||||
];
|
||||
|
||||
public function bootstrap(Tenancy $tenancy): void
|
||||
{
|
||||
foreach (static::$identificationMiddlewares as $middleware) {
|
||||
$originalOnFail = $middleware::$onFail;
|
||||
|
||||
$middleware::$onFail = function ($exception, $request, $next) use ($originalOnFail) {
|
||||
if (static::routeHasMiddleware($request->route(), static::$middlewareGroup)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if ($originalOnFail) {
|
||||
return $originalOnFail($exception, $request, $next);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static function routeHasMiddleware(Route $route, string $middleware): bool
|
||||
{
|
||||
/** @var array $routeMiddleware */
|
||||
$routeMiddleware = $route->middleware();
|
||||
|
||||
if (in_array($middleware, $routeMiddleware, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Loop one level deep and check if the route's middleware
|
||||
// groups have the searched middleware group inside them
|
||||
$middlewareGroups = Router::getMiddlewareGroups();
|
||||
foreach ($route->gatherMiddleware() as $inner) {
|
||||
if (! $inner instanceof Closure && isset($middlewareGroups[$inner]) && in_array($middleware, $middlewareGroups[$inner], true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,11 @@ class InitializeTenancyByDomain extends IdentificationMiddleware
|
|||
/** @return \Illuminate\Http\Response|mixed */
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
if (in_array($request->getHost(), config('tenancy.central_domains', []), true)) {
|
||||
// Always bypass tenancy initialization when host is in central domains
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return $this->initializeTenancy(
|
||||
$request,
|
||||
$next,
|
||||
|
|
|
|||
|
|
@ -28,14 +28,13 @@ class InitializeTenancyByPath extends IdentificationMiddleware
|
|||
/** @return \Illuminate\Http\Response|mixed */
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
/** @var Route $route */
|
||||
$route = $request->route();
|
||||
$route = $this->route($request);
|
||||
|
||||
// Only initialize tenancy if tenant is the first parameter
|
||||
// We don't want to initialize tenancy if the tenant is
|
||||
// simply injected into some route controller action.
|
||||
if ($route->parameterNames()[0] === PathTenantResolver::tenantParameterName()) {
|
||||
$this->setDefaultTenantForRouteParametersWhenTenancyIsInitialized();
|
||||
$this->setDefaultTenantForRouteParametersWhenInitializingTenancy();
|
||||
|
||||
return $this->initializeTenancy(
|
||||
$request,
|
||||
|
|
@ -47,7 +46,26 @@ class InitializeTenancyByPath extends IdentificationMiddleware
|
|||
}
|
||||
}
|
||||
|
||||
protected function setDefaultTenantForRouteParametersWhenTenancyIsInitialized(): void
|
||||
protected function route(Request $request): Route
|
||||
{
|
||||
/** @var Route $route */
|
||||
$route = $request->route();
|
||||
|
||||
if (! $route) {
|
||||
// Create a fake $route instance that has enough information for this middleware's needs
|
||||
$route = new Route($request->method(), $request->getUri(), []);
|
||||
/**
|
||||
* getPathInfo() returns the path except the root domain.
|
||||
* We fetch the first parameter because tenant parameter is *always* first.
|
||||
*/
|
||||
$route->parameters[PathTenantResolver::tenantParameterName()] = explode('/', ltrim($request->getPathInfo(), '/'))[0];
|
||||
$route->parameterNames[] = PathTenantResolver::tenantParameterName();
|
||||
}
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
protected function setDefaultTenantForRouteParametersWhenInitializingTenancy(): void
|
||||
{
|
||||
Event::listen(InitializingTenancy::class, function (InitializingTenancy $event) {
|
||||
/** @var Tenant $tenant */
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain
|
|||
/** @return Response|mixed */
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
if (in_array($request->getHost(), config('tenancy.central_domains', []), true)) {
|
||||
// Always bypass tenancy initialization when host is in central domains
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$subdomain = $this->makeSubdomain($request->getHost());
|
||||
|
||||
if (is_object($subdomain) && $subdomain instanceof Exception) {
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PreventAccessFromCentralDomains
|
||||
{
|
||||
/**
|
||||
* Set this property if you want to customize the on-fail behavior.
|
||||
*/
|
||||
public static ?Closure $abortRequest;
|
||||
|
||||
/** @return \Illuminate\Http\Response|mixed */
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
if (in_array($request->getHost(), config('tenancy.central_domains'))) {
|
||||
$abortRequest = static::$abortRequest ?? function () {
|
||||
abort(404);
|
||||
};
|
||||
|
||||
return $abortRequest($request, $next);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
58
src/Middleware/PreventAccessFromUnwantedDomains.php
Normal file
58
src/Middleware/PreventAccessFromUnwantedDomains.php
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Route;
|
||||
use Illuminate\Support\Facades\Route as Router;
|
||||
|
||||
// todo come up with a better name
|
||||
class PreventAccessFromUnwantedDomains
|
||||
{
|
||||
/**
|
||||
* Set this property if you want to customize the on-fail behavior.
|
||||
*/
|
||||
public static ?Closure $abortRequest;
|
||||
|
||||
/** @return \Illuminate\Http\Response|mixed */
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
if ($this->routeHasMiddleware($request->route(), 'universal')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (in_array($request->getHost(), config('tenancy.central_domains'), true)) {
|
||||
$abortRequest = static::$abortRequest ?? function () {
|
||||
abort(404);
|
||||
};
|
||||
|
||||
return $abortRequest($request, $next);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
protected function routeHasMiddleware(Route $route, string $middleware): bool
|
||||
{
|
||||
/** @var array $routeMiddleware */
|
||||
$routeMiddleware = $route->middleware();
|
||||
|
||||
if (in_array($middleware, $routeMiddleware, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Loop one level deep and check if the route's middleware
|
||||
// groups have the searched middleware group inside them
|
||||
$middlewareGroups = Router::getMiddlewareGroups();
|
||||
foreach ($route->gatherMiddleware() as $inner) {
|
||||
if (! $inner instanceof Closure && isset($middlewareGroups[$inner]) && in_array($middleware, $middlewareGroups[$inner], true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -163,17 +163,17 @@ test('rollback command works', function () {
|
|||
expect(Schema::hasTable('users'))->toBeFalse();
|
||||
});
|
||||
|
||||
test('seed command works', function (){
|
||||
test('seed command works', function () {
|
||||
$tenant = Tenant::create();
|
||||
Artisan::call('tenants:migrate');
|
||||
|
||||
$tenant->run(function (){
|
||||
$tenant->run(function () {
|
||||
expect(DB::table('users')->count())->toBe(0);
|
||||
});
|
||||
|
||||
Artisan::call('tenants:seed', ['--class' => TestSeeder::class]);
|
||||
|
||||
$tenant->run(function (){
|
||||
$tenant->run(function () {
|
||||
$user = DB::table('users');
|
||||
expect($user->count())->toBe(1)
|
||||
->and($user->first()->email)->toBe('seeded@user');
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ beforeEach(function () {
|
|||
config(['tenancy.models.tenant' => DatabaseAndDomainTenant::class]);
|
||||
});
|
||||
|
||||
test('job delete domains successfully', function (){
|
||||
test('job delete domains successfully', function () {
|
||||
$tenant = DatabaseAndDomainTenant::create();
|
||||
|
||||
$tenant->domains()->create([
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ use Stancl\Tenancy\Database\Models;
|
|||
use Stancl\Tenancy\Database\Models\Domain;
|
||||
use Stancl\Tenancy\Exceptions\DomainOccupiedByOtherTenantException;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||
use Stancl\Tenancy\Features\UniversalRoutes;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
||||
|
||||
|
|
@ -95,7 +94,6 @@ test('throw correct exception when onFail is null and universal routes are enabl
|
|||
|
||||
// Enable UniversalRoute feature
|
||||
Route::middlewareGroup('universal', []);
|
||||
config(['tenancy.features' => [UniversalRoutes::class]]);
|
||||
|
||||
$this->withoutExceptionHandling()->get('http://foo.localhost/foo/abc/xyz');
|
||||
})->throws(TenantCouldNotBeIdentifiedOnDomainException::class);;
|
||||
|
|
|
|||
104
tests/EarlyIdentificationTest.php
Normal file
104
tests/EarlyIdentificationTest.php
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Contracts\Http\Kernel;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
||||
use Stancl\Tenancy\Tests\Etc\EarlyIdentification\Controller;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
|
||||
beforeEach(function () {
|
||||
config()->set([
|
||||
'tenancy.token' => 'central-abc123',
|
||||
]);
|
||||
|
||||
Event::listen(TenancyInitialized::class, function (TenancyInitialized $event) {
|
||||
config()->set([
|
||||
'tenancy.token' => $event->tenancy->tenant->getTenantKey() . '-abc123',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('early identification works with path identification', function () {
|
||||
app(Kernel::class)->pushMiddleware(InitializeTenancyByPath::class);
|
||||
|
||||
Route::group([
|
||||
'prefix' => '/{tenant}',
|
||||
], function () {
|
||||
Route::get('/foo', [Controller::class, 'index'])->name('foo');
|
||||
});
|
||||
|
||||
Tenant::create([
|
||||
'id' => 'acme',
|
||||
]);
|
||||
|
||||
$response = pest()->get('/acme/foo')->assertOk();
|
||||
|
||||
assertTenancyInitializedInEarlyIdentificationRequest($response->getContent());
|
||||
|
||||
// check if default parameter feature is working fine by asserting that the route WITHOUT the tenant parameter
|
||||
// matches the route WITH the tenant parameter
|
||||
expect(route('foo'))->toBe(route('foo', ['tenant' => 'acme']));
|
||||
});
|
||||
|
||||
test('early identification works with request data identification', function (string $type) {
|
||||
app(Kernel::class)->pushMiddleware(InitializeTenancyByRequestData::class);
|
||||
|
||||
Route::get('/foo', [Controller::class, 'index'])->name('foo');
|
||||
|
||||
$tenant = Tenant::create([
|
||||
'id' => 'acme',
|
||||
]);
|
||||
|
||||
if ($type === 'header') {
|
||||
$response = pest()->get('/foo', ['X-Tenant' => $tenant->id])->assertOk();
|
||||
} elseif ($type === 'queryParameter') {
|
||||
$response = pest()->get("/foo?tenant=$tenant->id")->assertOk();
|
||||
}
|
||||
|
||||
assertTenancyInitializedInEarlyIdentificationRequest($response->getContent());
|
||||
})->with([
|
||||
'using request header parameter' => 'header',
|
||||
'using request query parameter' => 'queryParameter'
|
||||
]);
|
||||
|
||||
// The name of this test is suffixed by the dataset — domain / subdomain / domainOrSubdomain identification
|
||||
test('early identification works', function (string $middleware, string $domain, string $url) {
|
||||
app(Kernel::class)->pushMiddleware($middleware);
|
||||
|
||||
config(['tenancy.tenant_model' => Tenant::class]);
|
||||
|
||||
Route::get('/foo', [Controller::class, 'index'])
|
||||
->middleware(PreventAccessFromUnwantedDomains::class)
|
||||
->name('foo');
|
||||
|
||||
$tenant = Tenant::create();
|
||||
|
||||
$tenant->domains()->create([
|
||||
'domain' => $domain,
|
||||
]);
|
||||
|
||||
$response = pest()->get($url)->assertOk();
|
||||
|
||||
assertTenancyInitializedInEarlyIdentificationRequest($response->getContent());
|
||||
})->with([
|
||||
'domain identification' => ['middleware' => InitializeTenancyByDomain::class, 'domain' => 'foo.test', 'url' => 'http://foo.test/foo'],
|
||||
'subdomain identification' => ['middleware' => InitializeTenancyBySubdomain::class, 'domain' => 'foo', 'url' => 'http://foo.localhost/foo'],
|
||||
'domainOrSubdomain identification using domain' => ['middleware' => InitializeTenancyByDomainOrSubdomain::class, 'domain' => 'foo.test', 'url' => 'http://foo.test/foo'],
|
||||
'domainOrSubdomain identification using subdomain' => ['middleware' => InitializeTenancyByDomainOrSubdomain::class, 'domain' => 'foo', 'url' => 'http://foo.localhost/foo'],
|
||||
]);
|
||||
|
||||
function assertTenancyInitializedInEarlyIdentificationRequest(string|false $string): void
|
||||
{
|
||||
expect($string)->toBe(tenant()->getTenantKey() . '-abc123'); // Assert that the service class returns tenant value
|
||||
expect(app()->make('additionalMiddlewareRunsInTenantContext'))->toBeTrue(); // Assert that middleware added in the controller constructor runs in tenant context
|
||||
expect(app()->make('controllerRunsInTenantContext'))->toBeTrue(); // Assert that tenancy is initialized in the controller constructor
|
||||
}
|
||||
16
tests/Etc/EarlyIdentification/AdditionalMiddleware.php
Normal file
16
tests/Etc/EarlyIdentification/AdditionalMiddleware.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Etc\EarlyIdentification;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AdditionalMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
app()->instance('additionalMiddlewareRunsInTenantContext', tenancy()->initialized);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
19
tests/Etc/EarlyIdentification/Controller.php
Normal file
19
tests/Etc/EarlyIdentification/Controller.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Etc\EarlyIdentification;
|
||||
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
public function __construct(public Service $service)
|
||||
{
|
||||
app()->instance('controllerRunsInTenantContext', tenancy()->initialized);
|
||||
$this->middleware(AdditionalMiddleware::class);
|
||||
}
|
||||
|
||||
public function index(): string
|
||||
{
|
||||
return $this->service->token;
|
||||
}
|
||||
}
|
||||
15
tests/Etc/EarlyIdentification/Service.php
Normal file
15
tests/Etc/EarlyIdentification/Service.php
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Tests\Etc\EarlyIdentification;
|
||||
|
||||
class Service
|
||||
{
|
||||
public string $token;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->token = config('tenancy.token');
|
||||
}
|
||||
}
|
||||
|
|
@ -52,12 +52,13 @@ test('onfail logic can be customized', function () {
|
|||
->assertSee('foo');
|
||||
});
|
||||
|
||||
test('localhost is not a valid subdomain', function () {
|
||||
test('archte.ch is not a valid subdomain', function () {
|
||||
pest()->expectException(NotASubdomainException::class);
|
||||
|
||||
// This gets routed to the app, but with a request domain of 'archte.ch'
|
||||
$this
|
||||
->withoutExceptionHandling()
|
||||
->get('http://localhost/foo/abc/xyz');
|
||||
->get('http://archte.ch/foo/abc/xyz');
|
||||
});
|
||||
|
||||
test('ip address is not a valid subdomain', function () {
|
||||
|
|
@ -65,7 +66,7 @@ test('ip address is not a valid subdomain', function () {
|
|||
|
||||
$this
|
||||
->withoutExceptionHandling()
|
||||
->get('http://127.0.0.1/foo/abc/xyz');
|
||||
->get('http://127.0.0.2/foo/abc/xyz');
|
||||
});
|
||||
|
||||
test('oninvalidsubdomain logic can be customized', function () {
|
||||
|
|
@ -81,7 +82,7 @@ test('oninvalidsubdomain logic can be customized', function () {
|
|||
|
||||
$this
|
||||
->withoutExceptionHandling()
|
||||
->get('http://127.0.0.1/foo/abc/xyz')
|
||||
->get('http://127.0.0.2/foo/abc/xyz')
|
||||
->assertSee('foo custom invalid subdomain handler');
|
||||
});
|
||||
|
||||
|
|
@ -106,26 +107,6 @@ test('we cant use a subdomain that doesnt belong to our central domains', functi
|
|||
->get('http://foo.localhost/foo/abc/xyz');
|
||||
});
|
||||
|
||||
test('central domain is not a subdomain', function () {
|
||||
config(['tenancy.central_domains' => [
|
||||
'localhost',
|
||||
]]);
|
||||
|
||||
$tenant = SubdomainTenant::create([
|
||||
'id' => 'acme',
|
||||
]);
|
||||
|
||||
$tenant->domains()->create([
|
||||
'domain' => 'acme',
|
||||
]);
|
||||
|
||||
pest()->expectException(NotASubdomainException::class);
|
||||
|
||||
$this
|
||||
->withoutExceptionHandling()
|
||||
->get('http://localhost/foo/abc/xyz');
|
||||
});
|
||||
|
||||
class SubdomainTenant extends Models\Tenant
|
||||
{
|
||||
use HasDomains;
|
||||
|
|
|
|||
|
|
@ -3,27 +3,24 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Stancl\Tenancy\Features\UniversalRoutes;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
|
||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||
use Illuminate\Contracts\Http\Kernel;
|
||||
|
||||
afterEach(function () {
|
||||
InitializeTenancyByDomain::$onFail = null;
|
||||
});
|
||||
test('a route can work in both central and tenant context', function (array $routeMiddleware, string|null $globalMiddleware) {
|
||||
if ($globalMiddleware) {
|
||||
app(Kernel::class)->pushMiddleware($globalMiddleware);
|
||||
}
|
||||
|
||||
test('a route can work in both central and tenant context', function () {
|
||||
Route::middlewareGroup('universal', []);
|
||||
config(['tenancy.features' => [UniversalRoutes::class]]);
|
||||
|
||||
Route::get('/foo', function () {
|
||||
return tenancy()->initialized
|
||||
? 'Tenancy is initialized.'
|
||||
: 'Tenancy is not initialized.';
|
||||
})->middleware(['universal', InitializeTenancyByDomain::class]);
|
||||
|
||||
pest()->get('http://localhost/foo')
|
||||
->assertSuccessful()
|
||||
->assertSee('Tenancy is not initialized.');
|
||||
})->middleware($routeMiddleware);
|
||||
|
||||
$tenant = Tenant::create([
|
||||
'id' => 'acme',
|
||||
|
|
@ -32,28 +29,33 @@ test('a route can work in both central and tenant context', function () {
|
|||
'domain' => 'acme.localhost',
|
||||
]);
|
||||
|
||||
pest()->get('http://acme.localhost/foo')
|
||||
pest()->get("http://localhost/foo")
|
||||
->assertSuccessful()
|
||||
->assertSee('Tenancy is not initialized.');
|
||||
|
||||
pest()->get("http://acme.localhost/foo")
|
||||
->assertSuccessful()
|
||||
->assertSee('Tenancy is initialized.');
|
||||
});
|
||||
})->with('identification types');
|
||||
|
||||
test('making one route universal doesnt make all routes universal', function () {
|
||||
Route::get('/bar', function () {
|
||||
return tenant('id');
|
||||
})->middleware(InitializeTenancyByDomain::class);
|
||||
test('making one route universal doesnt make all routes universal', function (array $routeMiddleware, string|null $globalMiddleware) {
|
||||
if ($globalMiddleware) {
|
||||
app(Kernel::class)->pushMiddleware($globalMiddleware);
|
||||
}
|
||||
|
||||
Route::middlewareGroup('universal', []);
|
||||
config(['tenancy.features' => [UniversalRoutes::class]]);
|
||||
|
||||
Route::get('/foo', function () {
|
||||
Route::middleware($routeMiddleware)->group(function () {
|
||||
Route::get('/nonuniversal', function () {
|
||||
return tenant('id');
|
||||
});
|
||||
|
||||
Route::get('/universal', function () {
|
||||
return tenancy()->initialized
|
||||
? 'Tenancy is initialized.'
|
||||
: 'Tenancy is not initialized.';
|
||||
})->middleware(['universal', InitializeTenancyByDomain::class]);
|
||||
|
||||
pest()->get('http://localhost/foo')
|
||||
->assertSuccessful()
|
||||
->assertSee('Tenancy is not initialized.');
|
||||
})->middleware('universal');
|
||||
});
|
||||
|
||||
$tenant = Tenant::create([
|
||||
'id' => 'acme',
|
||||
|
|
@ -62,16 +64,57 @@ test('making one route universal doesnt make all routes universal', function ()
|
|||
'domain' => 'acme.localhost',
|
||||
]);
|
||||
|
||||
pest()->get('http://acme.localhost/foo')
|
||||
pest()->get("http://localhost/universal")
|
||||
->assertSuccessful()
|
||||
->assertSee('Tenancy is not initialized.');
|
||||
|
||||
pest()->get("http://acme.localhost/universal")
|
||||
->assertSuccessful()
|
||||
->assertSee('Tenancy is initialized.');
|
||||
|
||||
tenancy()->end();
|
||||
|
||||
pest()->get('http://localhost/bar')
|
||||
->assertStatus(500);
|
||||
pest()->get('http://localhost/nonuniversal')
|
||||
->assertStatus(404);
|
||||
|
||||
pest()->get('http://acme.localhost/bar')
|
||||
pest()->get('http://acme.localhost/nonuniversal')
|
||||
->assertSuccessful()
|
||||
->assertSee('acme');
|
||||
});
|
||||
})->with([
|
||||
'early identification' => [
|
||||
'route_middleware' => [PreventAccessFromUnwantedDomains::class],
|
||||
'global_middleware' => InitializeTenancyByDomain::class,
|
||||
],
|
||||
'route-level identification' => [
|
||||
'route_middleware' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class],
|
||||
'global_middleware' => null,
|
||||
]
|
||||
]);
|
||||
|
||||
test('it throws correct exception when route is universal and tenant does not exist', function (array $routeMiddleware, string|null $globalMiddleware) {
|
||||
if ($globalMiddleware) {
|
||||
app(Kernel::class)->pushMiddleware($globalMiddleware);
|
||||
}
|
||||
|
||||
Route::middlewareGroup('universal', []);
|
||||
|
||||
Route::get('/foo', function () {
|
||||
return tenancy()->initialized
|
||||
? 'Tenancy is initialized.'
|
||||
: 'Tenancy is not initialized.';
|
||||
})->middleware($routeMiddleware);
|
||||
|
||||
pest()->expectException(TenantCouldNotBeIdentifiedOnDomainException::class);
|
||||
$this->withoutExceptionHandling()->get('http://acme.localhost/foo');
|
||||
})->with('identification types');
|
||||
|
||||
dataset('identification types', [
|
||||
'early identification' => [
|
||||
'route_middleware' => ['universal', PreventAccessFromUnwantedDomains::class],
|
||||
'global_middleware' => InitializeTenancyByDomain::class,
|
||||
],
|
||||
'route-level identification' => [
|
||||
'route_middleware' => ['universal', PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class],
|
||||
'global_middleware' => null,
|
||||
]
|
||||
]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue