1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 14:14:04 +00:00

Refactor early identification (#47)

* Make universal route logic part of tbe early ID trait

* Add requstHasTenant to prevent access MW, add todo@samuel

* Delete PathIdentificationManager, move the used methods appropriately

* Correct and refactor code related to the deleted PathIdentificationManager class

* Add docblock

* Fix code style (php-cs-fixer)

* refactor globalStackMiddleware()

* remove todos [ci skip]

* refactor routeMiddleware()

* revert bool assertions

* revert more changes

---------

Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
Co-authored-by: Samuel Štancl <samuel@archte.ch>
This commit is contained in:
lukinovec 2024-04-22 11:30:58 +02:00 committed by GitHub
parent b70cd0e531
commit 4e51cdbacb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 95 additions and 256 deletions

View file

@ -193,12 +193,11 @@ class TenancyServiceProvider extends ServiceProvider
* return RouteFacade::get('/livewire/livewire.js', $handle)->middleware(['universal']); * return RouteFacade::get('/livewire/livewire.js', $handle)->middleware(['universal']);
* }); * });
*/ */
if (InitializeTenancyByRequestData::inGlobalStack()) { if (InitializeTenancyByRequestData::inGlobalStack()) {
TenancyUrlGenerator::$prefixRouteNames = false; TenancyUrlGenerator::$prefixRouteNames = false;
} }
if (InitializeTenancyByPath::inGlobalStack()) { if (tenancy()->globalStackHasMiddleware(config('tenancy.identification.path_identification_middleware'))) {
TenancyUrlGenerator::$prefixRouteNames = true; TenancyUrlGenerator::$prefixRouteNames = true;
/** @var CloneRoutesAsTenant $cloneRoutes */ /** @var CloneRoutesAsTenant $cloneRoutes */

View file

@ -87,7 +87,17 @@ return [
Middleware\InitializeTenancyByDomainOrSubdomain::class, Middleware\InitializeTenancyByDomainOrSubdomain::class,
], ],
// todo@lukas docblock /**
* Identification middleware tenancy recognizes as path identification middleware.
*
* This is used during determining whether whether a path identification is used
* during operations specific to path identification, e.g. forgetting the tenant parameter in ForgetTenantParameter.
*
* If you're using a custom path identification middleware, add it here.
*
* @see \Stancl\Tenancy\Actions\CloneRoutesAsTenant
* @see \Stancl\Tenancy\Listeners\ForgetTenantParameter
*/
'path_identification_middleware' => [ 'path_identification_middleware' => [
Middleware\InitializeTenancyByPath::class, Middleware\InitializeTenancyByPath::class,
], ],

View file

@ -3,14 +3,14 @@
declare(strict_types=1); declare(strict_types=1);
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\PathIdentificationManager;
use Stancl\Tenancy\Controllers\TenantAssetController; use Stancl\Tenancy\Controllers\TenantAssetController;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
Route::get('/tenancy/assets/{path?}', TenantAssetController::class) Route::get('/tenancy/assets/{path?}', TenantAssetController::class)
->where('path', '(.*)') ->where('path', '(.*)')
->name('stancl.tenancy.asset'); ->name('stancl.tenancy.asset');
Route::prefix('/{' . PathIdentificationManager::getTenantParameterName() . '}/')->get('/tenancy/assets/{path?}', TenantAssetController::class) Route::prefix('/{' . PathTenantResolver::tenantParameterName() . '}/')->get('/tenancy/assets/{path?}', TenantAssetController::class)
->where('path', '(.*)') ->where('path', '(.*)')
->middleware('tenant') ->middleware('tenant')
->name(PathIdentificationManager::getTenantRouteNamePrefix() . 'stancl.tenancy.asset'); ->name(PathTenantResolver::tenantRouteNamePrefix() . 'stancl.tenancy.asset');

View file

@ -10,7 +10,7 @@ use Illuminate\Routing\Router;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Stancl\Tenancy\Enums\RouteMode; use Stancl\Tenancy\Enums\RouteMode;
use Stancl\Tenancy\PathIdentificationManager; use Stancl\Tenancy\Resolvers\PathTenantResolver;
/** /**
* The CloneRoutesAsTenant action clones * The CloneRoutesAsTenant action clones
@ -75,7 +75,7 @@ class CloneRoutesAsTenant
protected function getRoutesToClone(): Collection protected function getRoutesToClone(): Collection
{ {
$tenantParameterName = PathIdentificationManager::getTenantParameterName(); $tenantParameterName = PathTenantResolver::tenantParameterName();
/** /**
* Clone all routes that: * Clone all routes that:
@ -95,9 +95,10 @@ class CloneRoutesAsTenant
return false; return false;
} }
$routeHasPathIdentificationMiddleware = PathIdentificationManager::pathIdentificationOnRoute($route); $pathIdentificationMiddleware = config('tenancy.identification.path_identification_middleware');
$pathIdentificationMiddlewareInGlobalStack = PathIdentificationManager::pathIdentificationInGlobalStack(); $routeHasPathIdentificationMiddleware = tenancy()->routeHasMiddleware($route, $pathIdentificationMiddleware);
$routeHasNonPathIdentificationMiddleware = tenancy()->routeHasIdentificationMiddleware($route) && ! $routeHasPathIdentificationMiddleware; $routeHasNonPathIdentificationMiddleware = tenancy()->routeHasIdentificationMiddleware($route) && ! $routeHasPathIdentificationMiddleware;
$pathIdentificationMiddlewareInGlobalStack = tenancy()->globalStackHasMiddleware($pathIdentificationMiddleware);
/** /**
* The route should get cloned if: * The route should get cloned if:
@ -163,7 +164,7 @@ class CloneRoutesAsTenant
->filter(fn (string $middleware) => ! in_array($middleware, ['universal', 'clone'])) ->filter(fn (string $middleware) => ! in_array($middleware, ['universal', 'clone']))
->toArray(); ->toArray();
$tenantRouteNamePrefix = PathIdentificationManager::getTenantRouteNamePrefix(); $tenantRouteNamePrefix = PathTenantResolver::tenantRouteNamePrefix();
// Make sure the route name has the tenant route name prefix // Make sure the route name has the tenant route name prefix
$newRouteNamePrefix = $route->getName() $newRouteNamePrefix = $route->getName()
@ -173,7 +174,7 @@ class CloneRoutesAsTenant
return $action return $action
->put('as', $newRouteNamePrefix) ->put('as', $newRouteNamePrefix)
->put('middleware', $newRouteMiddleware) ->put('middleware', $newRouteMiddleware)
->put('prefix', '/{' . PathIdentificationManager::getTenantParameterName() . '}/' . $route->getPrefix()); ->put('prefix', '/{' . PathTenantResolver::tenantParameterName() . '}/' . $route->getPrefix());
})->toArray(); })->toArray();
/** @var Route $newRoute */ /** @var Route $newRoute */

View file

@ -14,6 +14,9 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Route as RouteFacade; use Illuminate\Support\Facades\Route as RouteFacade;
use Stancl\Tenancy\Enums\RouteMode; use Stancl\Tenancy\Enums\RouteMode;
/**
* @mixin \Stancl\Tenancy\Tenancy
*/
trait DealsWithRouteContexts trait DealsWithRouteContexts
{ {
/** /**
@ -107,46 +110,14 @@ trait DealsWithRouteContexts
} }
/** /**
* Check if the passed route has the passed middleware * Checks whether any of the passed middleware are present in the route's middleware stack.
* three layers deep explained in the annotation of getRouteMiddleware().
*/ */
public static function routeHasMiddleware(Route $route, string $middleware): bool public static function routeHasMiddleware(Route $route, string|array $middlewares): bool
{ {
return in_array($middleware, static::getRouteMiddleware($route)); $routeMiddleware = static::getRouteMiddleware($route);
}
public function routeIdentificationMiddleware(Route $route): string|null foreach (Arr::wrap($middlewares) as $middleware) {
{ if (in_array($middleware, $routeMiddleware)) {
foreach (static::getRouteMiddleware($route) as $routeMiddleware) {
if (in_array($routeMiddleware, static::middleware())) {
return $routeMiddleware;
}
}
return null;
}
public static function kernelIdentificationMiddleware(): string|null
{
/** @var Kernel $kernel */
$kernel = app(Kernel::class);
foreach (static::middleware() as $identificationMiddleware) {
if ($kernel->hasMiddleware($identificationMiddleware)) {
return $identificationMiddleware;
}
}
return null;
}
/**
* Check if a route has identification middleware.
*/
public static function routeHasIdentificationMiddleware(Route $route): bool
{
foreach (static::getRouteMiddleware($route) as $middleware) {
if (in_array($middleware, static::middleware())) {
return true; return true;
} }
} }
@ -155,22 +126,34 @@ trait DealsWithRouteContexts
} }
/** /**
* Check if route uses kernel identification (identification middleare is in the global stack and the route doesn't have route-level identification middleware). * Check if a route has identification middleware.
*/ */
public static function routeUsesKernelIdentification(Route $route): bool public static function routeHasIdentificationMiddleware(Route $route): bool
{ {
return ! static::routeHasIdentificationMiddleware($route) && static::kernelIdentificationMiddleware(); return static::routeHasMiddleware($route, static::middleware());
} }
/** /**
* Check if a route uses domain identification. * Check if route uses kernel identification (identification middleware is in the global stack and the route doesn't have route-level identification middleware).
*/ */
public static function routeHasDomainIdentificationMiddleware(Route $route): bool public static function routeUsesKernelIdentification(Route $route): bool
{ {
$routeMiddleware = static::getRouteMiddleware($route); return ! static::routeHasIdentificationMiddleware($route) &&
tenancy()->globalStackHasMiddleware(static::middleware());
}
foreach (config('tenancy.identification.domain_identification_middleware') as $middleware) { /**
if (in_array($middleware, $routeMiddleware)) { * Checks whether any of the passed middleware are present in the global middleware stack.
*
* @param class-string|array $identificationMiddleware
*/
public static function globalStackHasMiddleware(string|array $identificationMiddleware = []): bool
{
/** @var Kernel $kernel */
$kernel = app(Kernel::class);
foreach (Arr::wrap($identificationMiddleware) as $middleware) {
if ($kernel->hasMiddleware($middleware)) {
return true; return true;
} }
} }

View file

@ -4,12 +4,10 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Concerns; namespace Stancl\Tenancy\Concerns;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Routing\Route; use Illuminate\Routing\Route;
use Stancl\Tenancy\Enums\Context; use Stancl\Tenancy\Enums\Context;
use Stancl\Tenancy\Enums\RouteMode; use Stancl\Tenancy\Enums\RouteMode;
use Stancl\Tenancy\Exceptions\MiddlewareNotUsableWithUniversalRoutesException;
use Stancl\Tenancy\Middleware\IdentificationMiddleware; use Stancl\Tenancy\Middleware\IdentificationMiddleware;
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains; use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
@ -27,6 +25,19 @@ use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
*/ */
trait UsableWithEarlyIdentification trait UsableWithEarlyIdentification
{ {
/**
* Determine if the tenant is present in the incoming request.
*
* Because universal routes can be in any context (central/tenant),
* we use this to determine the context. We can't just check for
* the route's middleware to determine the route's context.
*
* For example, route '/foo' has the 'universal' and InitializeTenancyByRequestData middleware.
* When visiting the route, we should determine the context by the presence of the tenant payload.
* The context is tenant if the tenant parameter is present (e.g. '?tenant=foo'), otherwise the context is central.
*/
abstract public function requestHasTenant(Request $request): bool;
/** /**
* Skip middleware if the route is universal and uses path identification or if the route is universal and the context should be central. * Skip middleware if the route is universal and uses path identification or if the route is universal and the context should be central.
* Universal routes using path identification should get cloned using CloneRoutesAsTenant. * Universal routes using path identification should get cloned using CloneRoutesAsTenant.
@ -36,9 +47,6 @@ trait UsableWithEarlyIdentification
protected function shouldBeSkipped(Route $route): bool protected function shouldBeSkipped(Route $route): bool
{ {
if (tenancy()->routeIsUniversal($route) && $this instanceof IdentificationMiddleware) { if (tenancy()->routeIsUniversal($route) && $this instanceof IdentificationMiddleware) {
/** @phpstan-ignore-next-line */
throw_unless($this instanceof UsableWithUniversalRoutes, MiddlewareNotUsableWithUniversalRoutesException::class);
return $this->determineUniversalRouteContextFromRequest(request()) === Context::CENTRAL; return $this->determineUniversalRouteContextFromRequest(request()) === Context::CENTRAL;
} }
@ -51,10 +59,11 @@ trait UsableWithEarlyIdentification
// Now that we're sure the MW isn't used in the global MW stack, we determine whether to skip it // Now that we're sure the MW isn't used in the global MW stack, we determine whether to skip it
if ($this instanceof PreventAccessFromUnwantedDomains) { if ($this instanceof PreventAccessFromUnwantedDomains) {
// Skip access prevention if the route directly uses a non-domain identification middleware // Skip access prevention if the route directly uses a non-domain identification middleware
return tenancy()->routeHasIdentificationMiddleware($route) && ! tenancy()->routeHasDomainIdentificationMiddleware($route); return tenancy()->routeHasIdentificationMiddleware($route) &&
! tenancy()->routeHasMiddleware($route, config('tenancy.identification.domain_identification_middleware'));
} }
return $this->shouldIdentificationMiddlewareBeSkipped($route); return $this->shouldSkipIdentificationMiddleware($route);
} }
protected function determineUniversalRouteContextFromRequest(Request $request): Context protected function determineUniversalRouteContextFromRequest(Request $request): Context
@ -66,7 +75,6 @@ trait UsableWithEarlyIdentification
$globalIdentificationUsed = ! tenancy()->routeHasIdentificationMiddleware($route) && static::inGlobalStack(); $globalIdentificationUsed = ! tenancy()->routeHasIdentificationMiddleware($route) && static::inGlobalStack();
$routeLevelIdentificationUsed = tenancy()->routeHasMiddleware($route, static::class); $routeLevelIdentificationUsed = tenancy()->routeHasMiddleware($route, static::class);
/** @var UsableWithUniversalRoutes $this */
if (($globalIdentificationUsed || $routeLevelIdentificationUsed) && $this->requestHasTenant($request)) { if (($globalIdentificationUsed || $routeLevelIdentificationUsed) && $this->requestHasTenant($request)) {
return Context::TENANT; return Context::TENANT;
} }
@ -74,9 +82,9 @@ trait UsableWithEarlyIdentification
return Context::CENTRAL; return Context::CENTRAL;
} }
protected function shouldIdentificationMiddlewareBeSkipped(Route $route): bool protected function shouldSkipIdentificationMiddleware(Route $route): bool
{ {
if (! static::inGlobalStack()) { if (! tenancy()->globalStackHasMiddleware(static::class)) {
return false; return false;
} }
@ -109,6 +117,6 @@ trait UsableWithEarlyIdentification
public static function inGlobalStack(): bool public static function inGlobalStack(): bool
{ {
return app(Kernel::class)->hasMiddleware(static::class); return tenancy()->globalStackHasMiddleware(static::class);
} }
} }

View file

@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Concerns;
use Illuminate\Http\Request;
/**
* Identification middleware has to implement this in order to make universal routes work with it,.
*/
interface UsableWithUniversalRoutes
{
/**
* Determine if the tenant is present in the incoming request.
*
* Because universal routes can be in any context (central/tenant),
* we use this to determine the context. We can't just check for
* the route's middleware to determine the route's context.
*
* For example, route '/foo' has the 'universal' and InitializeTenancyByRequestData middleware.
* When visiting the route, we should determine the context by the presence of the tenant payload.
* The context is tenant if the tenant parameter is present (e.g. '?tenant=foo'), otherwise the context is central.
*/
public function requestHasTenant(Request $request): bool;
}

View file

@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Exceptions;
use Exception;
class MiddlewareNotUsableWithUniversalRoutesException extends Exception
{
public function __construct(string $message = '')
{
parent::__construct($message ?: 'Universal routes are usable only with identification middleware that implements UsableWithUniversalRoutes.');
}
}

View file

@ -6,7 +6,7 @@ namespace Stancl\Tenancy\Listeners;
use Illuminate\Routing\Events\RouteMatched; use Illuminate\Routing\Events\RouteMatched;
use Stancl\Tenancy\Enums\RouteMode; use Stancl\Tenancy\Enums\RouteMode;
use Stancl\Tenancy\PathIdentificationManager; use Stancl\Tenancy\Resolvers\PathTenantResolver;
/** /**
* Remove the tenant parameter from the matched route when path identification is used globally. * Remove the tenant parameter from the matched route when path identification is used globally.
@ -26,12 +26,13 @@ class ForgetTenantParameter
{ {
public function handle(RouteMatched $event): void public function handle(RouteMatched $event): void
{ {
$kernelPathIdentificationUsed = PathIdentificationManager::pathIdentificationInGlobalStack() && ! tenancy()->routeHasIdentificationMiddleware($event->route); $pathIdentificationInGlobalStack = tenancy()->globalStackHasMiddleware(config('tenancy.identification.path_identification_middleware'));
$kernelPathIdentificationUsed = $pathIdentificationInGlobalStack && ! tenancy()->routeHasIdentificationMiddleware($event->route);
$routeMode = tenancy()->getRouteMode($event->route); $routeMode = tenancy()->getRouteMode($event->route);
$routeModeIsTenantOrUniversal = $routeMode === RouteMode::TENANT || ($routeMode === RouteMode::UNIVERSAL && $event->route->hasParameter(PathIdentificationManager::getTenantParameterName())); $routeModeIsTenantOrUniversal = $routeMode === RouteMode::TENANT || ($routeMode === RouteMode::UNIVERSAL && $event->route->hasParameter(PathTenantResolver::tenantParameterName()));
if ($kernelPathIdentificationUsed && $routeModeIsTenantOrUniversal) { if ($kernelPathIdentificationUsed && $routeModeIsTenantOrUniversal) {
$event->route->forgetParameter(PathIdentificationManager::getTenantParameterName()); $event->route->forgetParameter(PathTenantResolver::tenantParameterName());
} }
} }
} }

View file

@ -7,11 +7,10 @@ namespace Stancl\Tenancy\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification; use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
use Stancl\Tenancy\Concerns\UsableWithUniversalRoutes;
use Stancl\Tenancy\Resolvers\DomainTenantResolver; use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tenancy; use Stancl\Tenancy\Tenancy;
class InitializeTenancyByDomain extends IdentificationMiddleware implements UsableWithUniversalRoutes class InitializeTenancyByDomain extends IdentificationMiddleware
{ {
use UsableWithEarlyIdentification; use UsableWithEarlyIdentification;

View file

@ -7,16 +7,14 @@ namespace Stancl\Tenancy\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification; use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
use Stancl\Tenancy\Concerns\UsableWithUniversalRoutes;
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException; use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
use Stancl\Tenancy\PathIdentificationManager;
use Stancl\Tenancy\Resolvers\PathTenantResolver; use Stancl\Tenancy\Resolvers\PathTenantResolver;
use Stancl\Tenancy\Tenancy; use Stancl\Tenancy\Tenancy;
/** /**
* @see Stancl\Tenancy\Listeners\ForgetTenantParameter * @see Stancl\Tenancy\Listeners\ForgetTenantParameter
*/ */
class InitializeTenancyByPath extends IdentificationMiddleware implements UsableWithUniversalRoutes class InitializeTenancyByPath extends IdentificationMiddleware
{ {
use UsableWithEarlyIdentification; use UsableWithEarlyIdentification;
@ -55,6 +53,6 @@ class InitializeTenancyByPath extends IdentificationMiddleware implements Usable
*/ */
public function requestHasTenant(Request $request): bool public function requestHasTenant(Request $request): bool
{ {
return tenancy()->getRoute($request)->hasParameter(PathIdentificationManager::getTenantParameterName()); return tenancy()->getRoute($request)->hasParameter(PathTenantResolver::tenantParameterName());
} }
} }

View file

@ -7,13 +7,12 @@ namespace Stancl\Tenancy\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification; use Stancl\Tenancy\Concerns\UsableWithEarlyIdentification;
use Stancl\Tenancy\Concerns\UsableWithUniversalRoutes;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
use Stancl\Tenancy\Overrides\TenancyUrlGenerator; use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
use Stancl\Tenancy\Resolvers\RequestDataTenantResolver; use Stancl\Tenancy\Resolvers\RequestDataTenantResolver;
use Stancl\Tenancy\Tenancy; use Stancl\Tenancy\Tenancy;
class InitializeTenancyByRequestData extends IdentificationMiddleware implements UsableWithUniversalRoutes class InitializeTenancyByRequestData extends IdentificationMiddleware
{ {
use UsableWithEarlyIdentification; use UsableWithEarlyIdentification;

View file

@ -67,4 +67,10 @@ class PreventAccessFromUnwantedDomains
{ {
return in_array($request->getHost(), config('tenancy.identification.central_domains'), true); return in_array($request->getHost(), config('tenancy.identification.central_domains'), true);
} }
// todo@samuel
public function requestHasTenant(Request $request): bool
{
return false;
}
} }

View file

@ -6,7 +6,7 @@ namespace Stancl\Tenancy\Overrides;
use Illuminate\Routing\UrlGenerator; use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Stancl\Tenancy\PathIdentificationManager; use Stancl\Tenancy\Resolvers\PathTenantResolver;
/** /**
* This class is used in place of the default UrlGenerator when UrlGeneratorBootstrapper is enabled. * This class is used in place of the default UrlGenerator when UrlGeneratorBootstrapper is enabled.
@ -104,7 +104,7 @@ class TenancyUrlGenerator extends UrlGenerator
*/ */
protected function prefixRouteName(string $name): string protected function prefixRouteName(string $name): string
{ {
$tenantPrefix = PathIdentificationManager::getTenantRouteNamePrefix(); $tenantPrefix = PathTenantResolver::tenantRouteNamePrefix();
if (static::$prefixRouteNames && ! str($name)->startsWith($tenantPrefix)) { if (static::$prefixRouteNames && ! str($name)->startsWith($tenantPrefix)) {
$name = str($name)->after($tenantPrefix)->prepend($tenantPrefix)->toString(); $name = str($name)->after($tenantPrefix)->prepend($tenantPrefix)->toString();
@ -118,6 +118,6 @@ class TenancyUrlGenerator extends UrlGenerator
*/ */
protected function addTenantParameter(array $parameters): array protected function addTenantParameter(array $parameters): array
{ {
return tenant() && static::$passTenantParameterToRoutes ? array_merge($parameters, [PathIdentificationManager::getTenantParameterName() => tenant()->getTenantKey()]) : $parameters; return tenant() && static::$passTenantParameterToRoutes ? array_merge($parameters, [PathTenantResolver::tenantParameterName() => tenant()->getTenantKey()]) : $parameters;
} }
} }

View file

@ -1,60 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy;
use Closure;
use Illuminate\Routing\Route;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
// todo a lot of duplicate logic with PathTenantResolver, ideally remove this class
class PathIdentificationManager
{
public static Closure|null $tenantParameterName = null;
public static Closure|null $tenantRouteNamePrefix = null;
/**
* Get the tenant parameter name using the static property.
* Default to PathTenantResolver::tenantParameterName().
*/
public static function getTenantParameterName(): string
{
return static::$tenantParameterName ? (static::$tenantParameterName)() : PathTenantResolver::tenantParameterName();
}
/**
* Get the tenant route name prefix using the static property.
* Default to PathTenantResolver::tenantRouteNamePrefix().
*/
public static function getTenantRouteNamePrefix(): string
{
return static::$tenantRouteNamePrefix ? (static::$tenantRouteNamePrefix)() : PathTenantResolver::tenantRouteNamePrefix();
}
public static function pathIdentificationOnRoute(Route $route): bool
{
return static::checkPathIdentificationMiddleware(fn ($middleware) => tenancy()->routeHasMiddleware($route, $middleware));
}
public static function pathIdentificationInGlobalStack(): bool
{
return static::checkPathIdentificationMiddleware(fn ($middleware) => $middleware::inGlobalStack());
}
protected static function checkPathIdentificationMiddleware(Closure $closure): bool
{
foreach (static::getPathIdentificationMiddleware() as $middleware) {
if ($closure($middleware)) {
return true;
}
}
return false;
}
protected static function getPathIdentificationMiddleware(): array
{
return config('tenancy.identification.path_identification_middleware');
}
}

View file

@ -4,15 +4,14 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Resolvers; namespace Stancl\Tenancy\Resolvers;
use Stancl\Tenancy\Tenancy;
use Stancl\Tenancy\Contracts\Domain;
use Stancl\Tenancy\Contracts\Tenant;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Stancl\Tenancy\Contracts\SingleDomainTenant; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Eloquent\Relations\Relation;
use Stancl\Tenancy\Contracts\Domain;
use Stancl\Tenancy\Contracts\SingleDomainTenant;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
use Stancl\Tenancy\Tenancy;
class DomainTenantResolver extends Contracts\CachedTenantResolver class DomainTenantResolver extends Contracts\CachedTenantResolver
{ {

View file

@ -286,7 +286,7 @@ function getResolverArgument(string $resolver, string $tenantKey): string|Route
// Make the 'tenant' route parameter the tenant key // Make the 'tenant' route parameter the tenant key
// Setting the parameter on the $route->parameters property is required // Setting the parameter on the $route->parameters property is required
// Because $route->setParameter() throws an exception when $route->parameters is not set yet // Because $route->setParameter() throws an exception when $route->parameters is not set yet
$route->parameters[PathIdentificationManager::getTenantParameterName()] = $tenantKey; $route->parameters[PathTenantResolver::tenantParameterName()] = $tenantKey;
// Return the route instance with the tenant key as the 'tenant' parameter // Return the route instance with the tenant key as the 'tenant' parameter
return $route; return $route;

View file

@ -74,7 +74,7 @@ test('domain identification middleware is configurable', function() {
config(['tenancy.identification.domain_identification_middleware' => []]); config(['tenancy.identification.domain_identification_middleware' => []]);
expect(tenancy()->routeHasDomainIdentificationMiddleware($route))->toBeFalse(); expect(tenancy()->routeHasMiddleware($route, config('tenancy.identification.domain_identification_middleware')))->toBeFalse();
// Set domain identification middleware list back to default // Set domain identification middleware list back to default
config(['tenancy.identification.domain_identification_middleware' => [ config(['tenancy.identification.domain_identification_middleware' => [
@ -83,5 +83,5 @@ test('domain identification middleware is configurable', function() {
InitializeTenancyByDomainOrSubdomain::class, InitializeTenancyByDomainOrSubdomain::class,
]]); ]]);
expect(tenancy()->routeHasDomainIdentificationMiddleware($route))->toBeTrue(); expect(tenancy()->routeHasMiddleware($route, config('tenancy.identification.domain_identification_middleware')))->toBeTrue();
}); });

View file

@ -21,7 +21,6 @@ use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains; use Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain; use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
use Stancl\Tenancy\Exceptions\MiddlewareNotUsableWithUniversalRoutesException;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
test('a route can be universal using domain identification', function (array $routeMiddleware, array $globalMiddleware) { test('a route can be universal using domain identification', function (array $routeMiddleware, array $globalMiddleware) {
@ -321,29 +320,6 @@ test('tenant resolver methods return the correct names for configured values', f
['tenant_route_name_prefix', 'prefix'] ['tenant_route_name_prefix', 'prefix']
]); ]);
test('identification middleware works with universal routes only when it implements MiddlewareUsableWithUniversalRoutes', function () {
$tenantKey = Tenant::create()->getTenantKey();
$routeAction = fn () => tenancy()->initialized ? $tenantKey : 'Tenancy is not initialized.';
// Route with the package's request data identification middleware implements MiddlewareUsableWithUniversalRoutes
RouteFacade::get('/universal-route', $routeAction)->middleware(['universal', InitializeTenancyByRequestData::class]);
// Routes with custom request data identification middleware does not implement MiddlewareUsableWithUniversalRoutes
RouteFacade::get('/custom-mw-universal-route', $routeAction)->middleware(['universal', CustomMiddleware::class]);
RouteFacade::get('/custom-mw-tenant-route', $routeAction)->middleware(['tenant', CustomMiddleware::class]);
// Ensure the custom identification middleware works with non-universal routes
// This is tested here because this is the only test where the custom MW is used
// No exception is thrown for this request since the route uses the TENANT middleware, not the UNIVERSAL middleware
pest()->get('http://localhost/custom-mw-tenant-route?tenant=' . $tenantKey)->assertOk()->assertSee($tenantKey);
pest()->get('http://localhost/universal-route')->assertOk();
pest()->get('http://localhost/universal-route?tenant=' . $tenantKey)->assertOk()->assertSee($tenantKey);
pest()->expectException(MiddlewareNotUsableWithUniversalRoutesException::class);
$this->withoutExceptionHandling()->get('http://localhost/custom-mw-universal-route');
});
foreach ([ foreach ([
'domain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class], 'domain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyByDomain::class],
'subdomain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class], 'subdomain identification types' => [PreventAccessFromUnwantedDomains::class, InitializeTenancyBySubdomain::class],
@ -370,45 +346,6 @@ foreach ([
]); ]);
} }
class CustomMiddleware extends IdentificationMiddleware
{
use UsableWithEarlyIdentification;
public static string $header = 'X-Tenant';
public static string $cookie = 'X-Tenant';
public static string $queryParameter = 'tenant';
public function __construct(
protected Tenancy $tenancy,
protected RequestDataTenantResolver $resolver,
) {
}
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
if ($this->shouldBeSkipped(tenancy()->getRoute($request))) {
// Allow accessing central route in kernel identification
return $next($request);
}
return $this->initializeTenancy($request, $next, $this->getPayload($request));
}
protected function getPayload(Request $request): string|array|null
{
if (static::$header && $request->hasHeader(static::$header)) {
return $request->header(static::$header);
} elseif (static::$queryParameter && $request->has(static::$queryParameter)) {
return $request->get(static::$queryParameter);
} elseif (static::$cookie && $request->hasCookie(static::$cookie)) {
return $request->cookie(static::$cookie);
}
return null;
}
}
class Controller extends BaseController class Controller extends BaseController
{ {
public function __invoke() public function __invoke()