mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 23:14:03 +00:00
Identification middleware & tests
This commit is contained in:
parent
a17727b437
commit
8ea4940f34
18 changed files with 362 additions and 174 deletions
|
|
@ -19,6 +19,11 @@ return [
|
||||||
//
|
//
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'central_domains' => [
|
||||||
|
'127.0.0.1',
|
||||||
|
'localhost',
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
'storage' => [
|
'storage' => [
|
||||||
'data_column' => 'data',
|
'data_column' => 'data',
|
||||||
|
|
@ -232,6 +237,6 @@ return [
|
||||||
* Middleware pushed to the global middleware stack.
|
* Middleware pushed to the global middleware stack.
|
||||||
*/
|
*/
|
||||||
'global_middleware' => [ // todo get rid of this
|
'global_middleware' => [ // todo get rid of this
|
||||||
Stancl\Tenancy\Middleware\InitializeTenancy::class,
|
// Stancl\Tenancy\Middleware\InitializeTenancy::class,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
13
src/Contracts/Domain.php
Normal file
13
src/Contracts/Domain.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property-read Tenant $tenant
|
||||||
|
*
|
||||||
|
* @see \Stancl\Tenancy\Database\Models\Domain
|
||||||
|
*/
|
||||||
|
interface Domain
|
||||||
|
{
|
||||||
|
public function tenant();
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,8 @@ use Stancl\Tenancy\Events\DomainCreated;
|
||||||
use Stancl\Tenancy\Events\DomainDeleted;
|
use Stancl\Tenancy\Events\DomainDeleted;
|
||||||
use Stancl\Tenancy\Events\DomainSaved;
|
use Stancl\Tenancy\Events\DomainSaved;
|
||||||
use Stancl\Tenancy\Events\DomainUpdated;
|
use Stancl\Tenancy\Events\DomainUpdated;
|
||||||
use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException;
|
use Stancl\Tenancy\Exceptions\DomainOccupiedByOtherTenantException;
|
||||||
|
use Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property string $domain
|
* @property string $domain
|
||||||
|
|
@ -16,7 +17,7 @@ use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException;
|
||||||
*
|
*
|
||||||
* @property-read Tenant $tenant
|
* @property-read Tenant $tenant
|
||||||
*/
|
*/
|
||||||
class Domain extends Model
|
class Domain extends Model implements Contracts\Domain
|
||||||
{
|
{
|
||||||
public $guarded = [];
|
public $guarded = [];
|
||||||
public $casts = [
|
public $casts = [
|
||||||
|
|
@ -28,7 +29,7 @@ class Domain extends Model
|
||||||
$ensureDomainIsNotOccupied = function (Domain $self) {
|
$ensureDomainIsNotOccupied = function (Domain $self) {
|
||||||
if ($domain = Domain::where('domain', $self->domain)->first()) {
|
if ($domain = Domain::where('domain', $self->domain)->first()) {
|
||||||
if ($domain->getKey() !== $self->getKey()) {
|
if ($domain->getKey() !== $self->getKey()) {
|
||||||
throw new DomainsOccupiedByOtherTenantException;
|
throw new DomainOccupiedByOtherTenantException($self->domain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
15
src/Exceptions/DomainOccupiedByOtherTenantException.php
Normal file
15
src/Exceptions/DomainOccupiedByOtherTenantException.php
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class DomainOccupiedByOtherTenantException extends Exception
|
||||||
|
{
|
||||||
|
public function __construct($domain)
|
||||||
|
{
|
||||||
|
parent::__construct("The $domain domain is occupied by another tenant.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Exceptions;
|
|
||||||
|
|
||||||
use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException;
|
|
||||||
|
|
||||||
class DomainsOccupiedByOtherTenantException extends TenantCannotBeCreatedException
|
|
||||||
{
|
|
||||||
public function reason(): string
|
|
||||||
{
|
|
||||||
return "One or more of the tenant's domains are already occupied by another tenant.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13
src/Exceptions/NotASubdomainException.php
Normal file
13
src/Exceptions/NotASubdomainException.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class NotASubdomainException extends Exception
|
||||||
|
{
|
||||||
|
public function __construct(string $hostname)
|
||||||
|
{
|
||||||
|
parent::__construct("Hostname $hostname does not include a subdomain.");
|
||||||
|
}
|
||||||
|
}
|
||||||
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\Resolvers\PathTenantResolver;
|
||||||
use Stancl\Tenancy\Tenancy;
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
class InitializeTenancyByPath
|
class InitializeTenancyByPath extends IdentificationMiddleware
|
||||||
{
|
{
|
||||||
/** @var Tenancy */
|
/** @var Tenancy */
|
||||||
protected $tenancy;
|
protected $tenancy;
|
||||||
|
|
@ -27,14 +27,15 @@ class InitializeTenancyByPath
|
||||||
/** @var Route $route */
|
/** @var Route $route */
|
||||||
$route = $request->route();
|
$route = $request->route();
|
||||||
|
|
||||||
|
// todo test the behavior described by the comment
|
||||||
// Only initialize tenancy if tenant is the first parameter
|
// Only initialize tenancy if tenant is the first parameter
|
||||||
// We don't want to initialize tenancy if the tenant is
|
// We don't want to initialize tenancy if the tenant is
|
||||||
// simply injected into some route controller action.
|
// simply injected into some route controller action.
|
||||||
if ($route->parameterNames()[0] === 'tenant') {
|
if ($route->parameterNames()[0] === 'tenant') {
|
||||||
$this->tenancy->initialize(
|
return $this->initializeTenancy(
|
||||||
$this->resolver->resolve($route)
|
$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);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Resolvers;
|
namespace Stancl\Tenancy\Resolvers;
|
||||||
|
|
||||||
|
use Stancl\Tenancy\Contracts\Domain;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
use Stancl\Tenancy\Contracts\TenantResolver;
|
use Stancl\Tenancy\Contracts\TenantResolver;
|
||||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||||
|
|
@ -10,6 +11,7 @@ class DomainTenantResolver implements TenantResolver
|
||||||
{
|
{
|
||||||
public function resolve(...$args): Tenant
|
public function resolve(...$args): Tenant
|
||||||
{
|
{
|
||||||
|
/** @var Domain $domain */
|
||||||
$domain = config('tenancy.domain_model')::where('domain', $args[0])->first();
|
$domain = config('tenancy.domain_model')::where('domain', $args[0])->first();
|
||||||
|
|
||||||
if ($domain) {
|
if ($domain) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
namespace Stancl\Tenancy;
|
namespace Stancl\Tenancy;
|
||||||
|
|
||||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Database\Models\Tenant;
|
use Stancl\Tenancy\Database\Models\Tenant; // todo contract
|
||||||
|
|
||||||
class Tenancy
|
class Tenancy
|
||||||
{
|
{
|
||||||
|
|
@ -18,6 +18,11 @@ class Tenancy
|
||||||
|
|
||||||
public function initialize(Tenant $tenant): void
|
public function initialize(Tenant $tenant): void
|
||||||
{
|
{
|
||||||
|
// todo the id is something that should be on the contract, with a method
|
||||||
|
if ($this->initialized && $this->tenant->id === $tenant->id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->tenant = $tenant;
|
$this->tenant = $tenant;
|
||||||
|
|
||||||
$this->initialized = true;
|
$this->initialized = true;
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests\v3;
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
use Stancl\Tenancy\Database\Models;
|
use Stancl\Tenancy\Database\Models;
|
||||||
use Stancl\Tenancy\Database\Models\Concerns\HasDomains;
|
use Stancl\Tenancy\Database\Models\Concerns\HasDomains;
|
||||||
use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException;
|
use Stancl\Tenancy\Exceptions\DomainOccupiedByOtherTenantException;
|
||||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||||
|
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||||
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
||||||
use Stancl\Tenancy\Tests\TestCase;
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
|
|
@ -15,6 +17,14 @@ class DomainTest extends TestCase
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
Route::group([
|
||||||
|
'middleware' => InitializeTenancyByDomain::class,
|
||||||
|
], function () {
|
||||||
|
Route::get('/foo/{a}/{b}', function ($a, $b) {
|
||||||
|
return "$a + $b";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
config(['tenancy.tenant_model' => Tenant::class]);
|
config(['tenancy.tenant_model' => Tenant::class]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,7 +56,7 @@ class DomainTest extends TestCase
|
||||||
|
|
||||||
$tenant2 = Tenant::create();
|
$tenant2 = Tenant::create();
|
||||||
|
|
||||||
$this->expectException(DomainsOccupiedByOtherTenantException::class);
|
$this->expectException(DomainOccupiedByOtherTenantException::class);
|
||||||
$tenant2->domains()->create([
|
$tenant2->domains()->create([
|
||||||
'domain' => 'foo.localhost',
|
'domain' => 'foo.localhost',
|
||||||
]);
|
]);
|
||||||
|
|
@ -61,29 +71,40 @@ class DomainTest extends TestCase
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function tenancy_is_initialized_prior_to_controller_constructors()
|
public function tenant_can_be_identified_by_domain()
|
||||||
{
|
{
|
||||||
// todo
|
$tenant = Tenant::create([
|
||||||
$this->assertTrue(app('tenancy_was_initialized_in_constructor'));
|
'id' => 'acme',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tenant->domains()->create([
|
||||||
|
'domain' => 'foo.localhost',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertFalse(tenancy()->initialized);
|
||||||
|
|
||||||
|
$this
|
||||||
|
->get('http://foo.localhost/foo/abc/xyz')
|
||||||
|
->assertSee('abc + xyz');
|
||||||
|
|
||||||
$this->assertTrue(tenancy()->initialized);
|
$this->assertTrue(tenancy()->initialized);
|
||||||
$this->assertSame('acme', tenant('id'));
|
$this->assertSame('acme', tenant('id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function onfail_logic_can_be_customized()
|
||||||
|
{
|
||||||
|
InitializeTenancyByDomain::$onFail = function () {
|
||||||
|
return 'foo';
|
||||||
|
};
|
||||||
|
|
||||||
|
$this
|
||||||
|
->get('http://foo.localhost/foo/abc/xyz')
|
||||||
|
->assertSee('foo');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tenant extends Models\Tenant
|
class Tenant extends Models\Tenant
|
||||||
{
|
{
|
||||||
use HasDomains;
|
use HasDomains;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestController
|
|
||||||
{
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
app()->instance('tenancy_was_initialized_in_constructor', tenancy()->initialized);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function index()
|
|
||||||
{
|
|
||||||
return 'foo';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,13 +10,6 @@ use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
class PathIdentificationTest extends TestCase
|
class PathIdentificationTest extends TestCase
|
||||||
{
|
{
|
||||||
public function getEnvironmentSetup($app)
|
|
||||||
{
|
|
||||||
parent::getEnvironmentSetUp($app);
|
|
||||||
|
|
||||||
config(['tenancy.global_middleware' => []]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUp(): void
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
@ -28,8 +21,6 @@ class PathIdentificationTest extends TestCase
|
||||||
Route::get('/foo/{a}/{b}', function ($a, $b) {
|
Route::get('/foo/{a}/{b}', function ($a, $b) {
|
||||||
return "$a + $b";
|
return "$a + $b";
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/bar', [TestController::class, 'index']);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,8 +33,7 @@ class PathIdentificationTest extends TestCase
|
||||||
|
|
||||||
$this->assertFalse(tenancy()->initialized);
|
$this->assertFalse(tenancy()->initialized);
|
||||||
|
|
||||||
$this
|
$this->get('/acme/foo/abc/xyz');
|
||||||
->get('/acme/foo/abc/xyz');
|
|
||||||
|
|
||||||
$this->assertTrue(tenancy()->initialized);
|
$this->assertTrue(tenancy()->initialized);
|
||||||
$this->assertSame('acme', tenant('id'));
|
$this->assertSame('acme', tenant('id'));
|
||||||
|
|
@ -77,4 +67,16 @@ class PathIdentificationTest extends TestCase
|
||||||
|
|
||||||
$this->assertFalse(tenancy()->initialized);
|
$this->assertFalse(tenancy()->initialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function onfail_logic_can_be_customized()
|
||||||
|
{
|
||||||
|
InitializeTenancyByPath::$onFail = function () {
|
||||||
|
return 'foo';
|
||||||
|
};
|
||||||
|
|
||||||
|
$this
|
||||||
|
->get('/acme/foo/abc/xyz')
|
||||||
|
->assertSee('foo');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
101
tests/v3/SubdomainTest.php
Normal file
101
tests/v3/SubdomainTest.php
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Stancl\Tenancy\Database\Models;
|
||||||
|
use Stancl\Tenancy\Database\Models\Concerns\HasDomains;
|
||||||
|
use Stancl\Tenancy\Exceptions\NotASubdomainException;
|
||||||
|
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
|
class SubdomainTest extends TestCase
|
||||||
|
{
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
Route::group([
|
||||||
|
'middleware' => InitializeTenancyBySubdomain::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()
|
||||||
|
{
|
||||||
|
$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 onfail_logic_can_be_customized()
|
||||||
|
{
|
||||||
|
InitializeTenancyBySubdomain::$onFail = function () {
|
||||||
|
return 'foo';
|
||||||
|
};
|
||||||
|
|
||||||
|
$this
|
||||||
|
->get('http://foo.localhost/foo/abc/xyz')
|
||||||
|
->assertSee('foo');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function localhost_is_not_a_valid_subdomain()
|
||||||
|
{
|
||||||
|
$this->expectException(NotASubdomainException::class);
|
||||||
|
|
||||||
|
$this
|
||||||
|
->withoutExceptionHandling()
|
||||||
|
->get('http://localhost/foo/abc/xyz');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function ip_address_is_not_a_valid_subdomain()
|
||||||
|
{
|
||||||
|
$this->expectException(NotASubdomainException::class);
|
||||||
|
|
||||||
|
$this
|
||||||
|
->withoutExceptionHandling()
|
||||||
|
->get('http://127.0.0.1/foo/abc/xyz');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function oninvalidsubdomain_logic_can_be_customized()
|
||||||
|
{
|
||||||
|
// in this case, we need to return a response instance
|
||||||
|
// since a string would be treated as the subdomain
|
||||||
|
InitializeTenancyBySubdomain::$onInvalidSubdomain = function () {
|
||||||
|
return response('foo custom invalid subdomain handler');
|
||||||
|
};
|
||||||
|
|
||||||
|
$this
|
||||||
|
->withoutExceptionHandling()
|
||||||
|
->get('http://127.0.0.1/foo/abc/xyz')
|
||||||
|
->assertSee('foo custom invalid subdomain handler');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tenant extends Models\Tenant
|
||||||
|
{
|
||||||
|
use HasDomains;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue