mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 16:24:04 +00:00
Identification middleware & tests
This commit is contained in:
parent
a17727b437
commit
8ea4940f34
18 changed files with 362 additions and 174 deletions
36
src/Middleware/IdentificationMiddleware.php
Normal file
36
src/Middleware/IdentificationMiddleware.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Stancl\Tenancy\Middleware;
|
||||
|
||||
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
||||
use Stancl\Tenancy\Contracts\TenantResolver;
|
||||
use Stancl\Tenancy\Tenancy;
|
||||
|
||||
abstract class IdentificationMiddleware
|
||||
{
|
||||
/** @var callable */
|
||||
public static $onFail;
|
||||
|
||||
/** @var Tenancy */
|
||||
protected $tenancy;
|
||||
|
||||
/** @var TenantResolver */
|
||||
protected $resolver;
|
||||
|
||||
public function initializeTenancy($request, $next, ...$resolverArguments)
|
||||
{
|
||||
try {
|
||||
$this->tenancy->initialize(
|
||||
$this->resolver->resolve(...$resolverArguments)
|
||||
);
|
||||
} catch (TenantCouldNotBeIdentifiedException $e) {
|
||||
$onFail = static::$onFail ?? function ($e) {
|
||||
throw $e;
|
||||
};
|
||||
|
||||
return $onFail($e, $request, $next);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException;
|
||||
|
||||
class InitializeTenancy
|
||||
{
|
||||
/** @var callable */
|
||||
protected $onFail;
|
||||
|
||||
public function __construct(callable $onFail = null)
|
||||
{
|
||||
$this->onFail = $onFail ?? function ($e) {
|
||||
throw $e;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
if (tenancy()->initialized) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (! in_array($request->getHost(), config('tenancy.exempt_domains', []), true)) {
|
||||
try {
|
||||
tenancy()->init($request->getHost());
|
||||
} catch (TenantCouldNotBeIdentifiedException $e) {
|
||||
return ($this->onFail)($e, $request, $next);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
41
src/Middleware/InitializeTenancyByDomain.php
Normal file
41
src/Middleware/InitializeTenancyByDomain.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
||||
use Stancl\Tenancy\Tenancy;
|
||||
|
||||
class InitializeTenancyByDomain extends IdentificationMiddleware
|
||||
{
|
||||
/** @var callable|null */
|
||||
public static $onFail;
|
||||
|
||||
/** @var Tenancy */
|
||||
protected $tenancy;
|
||||
|
||||
/** @var DomainTenantResolver */
|
||||
protected $resolver;
|
||||
|
||||
public function __construct(Tenancy $tenancy, DomainTenantResolver $resolver)
|
||||
{
|
||||
$this->tenancy = $tenancy;
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
return $this->initializeTenancy(
|
||||
$request, $next, $request->getHost()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ use Illuminate\Routing\Route;
|
|||
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
||||
use Stancl\Tenancy\Tenancy;
|
||||
|
||||
class InitializeTenancyByPath
|
||||
class InitializeTenancyByPath extends IdentificationMiddleware
|
||||
{
|
||||
/** @var Tenancy */
|
||||
protected $tenancy;
|
||||
|
|
@ -27,14 +27,15 @@ class InitializeTenancyByPath
|
|||
/** @var Route $route */
|
||||
$route = $request->route();
|
||||
|
||||
// todo test the behavior described by the comment
|
||||
// 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] === 'tenant') {
|
||||
$this->tenancy->initialize(
|
||||
$this->resolver->resolve($route)
|
||||
return $this->initializeTenancy(
|
||||
$request, $next, $route
|
||||
);
|
||||
}
|
||||
} // todo else case should probably throw exception about malformed route? or do we just leave that as the developer's responsibility?
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
|
|
|||
67
src/Middleware/InitializeTenancyBySubdomain.php
Normal file
67
src/Middleware/InitializeTenancyBySubdomain.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Response;
|
||||
use Stancl\Tenancy\Exceptions\NotASubdomainException;
|
||||
|
||||
class InitializeTenancyBySubdomain extends InitializeTenancyByDomain
|
||||
{
|
||||
/** @var callable|null */
|
||||
public static $onInvalidSubdomain;
|
||||
|
||||
/**
|
||||
* The index of the subdomain fragment in the hostname
|
||||
* split by `.`. 0 for first fragment, 1 if you prefix
|
||||
* your subdomain fragments with `www`.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $subdomainIndex = 0;
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$subdomain = $this->makeSubdomain($request->getHost());
|
||||
|
||||
// If a non-string, like a Response instance was returned
|
||||
// from makeSubdomain() - due to NotASubDomainException
|
||||
// being thrown, we abort by returning the value now.
|
||||
if (! is_string($subdomain)) {
|
||||
return $subdomain;
|
||||
}
|
||||
|
||||
return $this->initializeTenancy(
|
||||
$request, $next, $subdomain
|
||||
);
|
||||
}
|
||||
|
||||
/** @return string|Response|mixed */
|
||||
protected function makeSubdomain(string $hostname)
|
||||
{
|
||||
$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])) {
|
||||
$handle = static::$onInvalidSubdomain ?? function ($e) {
|
||||
throw $e;
|
||||
};
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Routing\Route;
|
||||
use Illuminate\Support\Facades\Route as Router;
|
||||
|
||||
/**
|
||||
* Prevent access from tenant domains to central routes and vice versa.
|
||||
*/
|
||||
class PreventAccessFromTenantDomains
|
||||
{
|
||||
/** @var callable */
|
||||
protected $central404;
|
||||
|
||||
public function __construct(callable $central404 = null)
|
||||
{
|
||||
$this->central404 = $central404 ?? function () {
|
||||
return 404;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
// If the route is universal, always let the request pass.
|
||||
if ($this->routeHasMiddleware($request->route(), 'universal')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// If the domain is not in exempt domains, it's a tenant domain.
|
||||
// Tenant domains can't have routes without tenancy middleware.
|
||||
$isExemptDomain = in_array($request->getHost(), config('tenancy.exempt_domains'));
|
||||
$isTenantDomain = ! $isExemptDomain;
|
||||
|
||||
$isTenantRoute = $this->routeHasMiddleware($request->route(), 'tenancy');
|
||||
|
||||
if ($isTenantDomain && ! $isTenantRoute) { // accessing web routes from tenant domains
|
||||
return redirect(config('tenancy.home_url'));
|
||||
}
|
||||
|
||||
if ($isExemptDomain && $isTenantRoute) { // accessing tenant routes on web domains
|
||||
return ($this->central404)($request, $next);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
public static function routeHasMiddleware(Route $route, $middleware): bool
|
||||
{
|
||||
if (in_array($middleware, $route->middleware(), true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Loop one level deep and check if the route's middleware
|
||||
// groups have a `tenancy` 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue