mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-05 16:54:05 +00:00
Merge branch 'master' into bugfix/batch-bootstrapper
This commit is contained in:
commit
d1a8b741d1
16 changed files with 268 additions and 31 deletions
42
src/Bootstrappers/Integrations/ScoutTenancyBootstrapper.php
Normal file
42
src/Bootstrappers/Integrations/ScoutTenancyBootstrapper.php
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Bootstrappers\Integrations;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Config\Repository;
|
||||||
|
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
|
class ScoutTenancyBootstrapper implements TenancyBootstrapper
|
||||||
|
{
|
||||||
|
/** @var Repository */
|
||||||
|
protected $config;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $originalScoutPrefix;
|
||||||
|
|
||||||
|
public function __construct(Repository $config)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bootstrap(Tenant $tenant)
|
||||||
|
{
|
||||||
|
if (! isset($this->originalScoutPrefix)) {
|
||||||
|
$this->originalScoutPrefix = $this->config->get('scout.prefix');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->config->set('scout.prefix', $this->getTenantPrefix($tenant));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revert()
|
||||||
|
{
|
||||||
|
$this->config->set('scout.prefix', $this->originalScoutPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getTenantPrefix(Tenant $tenant): string
|
||||||
|
{
|
||||||
|
return (string) $tenant->getTenantKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,9 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Commands;
|
namespace Stancl\Tenancy\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Contracts\Console\Kernel;
|
||||||
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
|
|
||||||
class Run extends Command
|
class Run extends Command
|
||||||
{
|
{
|
||||||
|
|
@ -29,12 +31,27 @@ class Run extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
$argvInput = $this->ArgvInput();
|
||||||
|
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) use ($argvInput) {
|
||||||
$this->line("Tenant: {$tenant->getTenantKey()}");
|
$this->line("Tenant: {$tenant->getTenantKey()}");
|
||||||
|
|
||||||
Artisan::call($this->argument('commandname'));
|
$this->getLaravel()
|
||||||
$this->comment('Command output:');
|
->make(Kernel::class)
|
||||||
$this->info(Artisan::output());
|
->handle($argvInput, new ConsoleOutput);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get command as ArgvInput instance.
|
||||||
|
*/
|
||||||
|
protected function ArgvInput(): ArgvInput
|
||||||
|
{
|
||||||
|
// Convert string command to array
|
||||||
|
$subCommand = explode(' ', $this->argument('commandname'));
|
||||||
|
|
||||||
|
// Add "artisan" as first parameter because ArgvInput expects "artisan" as first parameter and later removes it
|
||||||
|
array_unshift($subCommand, 'artisan');
|
||||||
|
|
||||||
|
return new ArgvInput($subCommand);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,12 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Database\Models;
|
namespace Stancl\Tenancy\Database\Models;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Stancl\Tenancy\Database\Concerns\CentralConnection;
|
use Stancl\Tenancy\Database\Concerns\CentralConnection;
|
||||||
|
use Stancl\Tenancy\Exceptions\StatefulGuardRequiredException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property string $token
|
* @property string $token
|
||||||
|
|
@ -38,9 +41,15 @@ class ImpersonationToken extends Model
|
||||||
public static function booted(): void
|
public static function booted(): void
|
||||||
{
|
{
|
||||||
static::creating(function ($model) {
|
static::creating(function ($model) {
|
||||||
|
$authGuard = $model->auth_guard ?? config('auth.defaults.guard');
|
||||||
|
|
||||||
|
if (! Auth::guard($authGuard) instanceof StatefulGuard) {
|
||||||
|
throw new StatefulGuardRequiredException($authGuard);
|
||||||
|
}
|
||||||
|
|
||||||
$model->created_at = $model->created_at ?? $model->freshTimestamp();
|
$model->created_at = $model->created_at ?? $model->freshTimestamp();
|
||||||
$model->token = $model->token ?? Str::random(128);
|
$model->token = $model->token ?? Str::random(128);
|
||||||
$model->auth_guard = $model->auth_guard ?? config('auth.defaults.guard');
|
$model->auth_guard = $authGuard;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
src/Exceptions/StatefulGuardRequiredException.php
Normal file
15
src/Exceptions/StatefulGuardRequiredException.php
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class StatefulGuardRequiredException extends Exception
|
||||||
|
{
|
||||||
|
public function __construct(string $guardName)
|
||||||
|
{
|
||||||
|
parent::__construct("Cannot use a non-stateful guard ('$guardName'). A guard implementing the Illuminate\\Contracts\\Auth\\StatefulGuard interface is required.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,12 +14,16 @@ class CrossDomainRedirect implements Feature
|
||||||
{
|
{
|
||||||
RedirectResponse::macro('domain', function (string $domain) {
|
RedirectResponse::macro('domain', function (string $domain) {
|
||||||
/** @var RedirectResponse $this */
|
/** @var RedirectResponse $this */
|
||||||
|
|
||||||
// Replace first occurrence of the hostname fragment with $domain
|
|
||||||
$url = $this->getTargetUrl();
|
$url = $this->getTargetUrl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The original hostname in the redirect response.
|
||||||
|
*
|
||||||
|
* @var string $hostname
|
||||||
|
*/
|
||||||
$hostname = parse_url($url, PHP_URL_HOST);
|
$hostname = parse_url($url, PHP_URL_HOST);
|
||||||
$position = strpos($url, $hostname);
|
|
||||||
$this->setTargetUrl(substr_replace($url, $domain, $position, strlen($hostname)));
|
$this->setTargetUrl((string) str($url)->replace($hostname, $domain));
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ namespace Stancl\Tenancy\Middleware;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Route;
|
use Illuminate\Routing\Route;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
|
use Stancl\Tenancy\Events\InitializingTenancy;
|
||||||
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
|
use Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException;
|
||||||
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
use Stancl\Tenancy\Resolvers\PathTenantResolver;
|
||||||
use Stancl\Tenancy\Tenancy;
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
|
@ -37,6 +40,11 @@ class InitializeTenancyByPath extends IdentificationMiddleware
|
||||||
// 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] === PathTenantResolver::$tenantParameterName) {
|
if ($route->parameterNames()[0] === PathTenantResolver::$tenantParameterName) {
|
||||||
|
// Set tenant as a default parameter for the URLs in the current request
|
||||||
|
Event::listen(InitializingTenancy::class, function (InitializingTenancy $event) {
|
||||||
|
URL::defaults([PathTenantResolver::$tenantParameterName => $event->tenancy->tenant->getTenantKey()]);
|
||||||
|
});
|
||||||
|
|
||||||
return $this->initializeTenancy(
|
return $this->initializeTenancy(
|
||||||
$request,
|
$request,
|
||||||
$next,
|
$next,
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ class Tenancy
|
||||||
$tenants = is_string($tenants) ? [$tenants] : $tenants;
|
$tenants = is_string($tenants) ? [$tenants] : $tenants;
|
||||||
|
|
||||||
// Use all tenants if $tenants is falsey
|
// Use all tenants if $tenants is falsey
|
||||||
$tenants = $tenants ?: $this->model()->cursor();
|
$tenants = $tenants ?: $this->model()->cursor(); // todo0 phpstan thinks this isn't needed, but tests fail without it
|
||||||
|
|
||||||
$originalTenant = $this->tenant;
|
$originalTenant = $this->tenant;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,11 +58,15 @@ if (! function_exists('global_cache')) {
|
||||||
if (! function_exists('tenant_route')) {
|
if (! function_exists('tenant_route')) {
|
||||||
function tenant_route(string $domain, string $route, array $parameters = [], bool $absolute = true): string
|
function tenant_route(string $domain, string $route, array $parameters = [], bool $absolute = true): string
|
||||||
{
|
{
|
||||||
// replace the first occurrence of the hostname fragment with $domain
|
|
||||||
$url = route($route, $parameters, $absolute);
|
$url = route($route, $parameters, $absolute);
|
||||||
$hostname = parse_url($url, PHP_URL_HOST);
|
|
||||||
$position = strpos($url, $hostname);
|
|
||||||
|
|
||||||
return substr_replace($url, $domain, $position, strlen($hostname));
|
/**
|
||||||
|
* The original hostname in the generated route.
|
||||||
|
*
|
||||||
|
* @var string $hostname
|
||||||
|
*/
|
||||||
|
$hostname = parse_url($url, PHP_URL_HOST);
|
||||||
|
|
||||||
|
return (string) str($url)->replace($hostname, $domain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||||
use Stancl\Tenancy\Tests\Etc\ExampleSeeder;
|
use Stancl\Tenancy\Tests\Etc\ExampleSeeder;
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||||
|
use Stancl\Tenancy\Tests\Etc\User;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
|
||||||
|
|
@ -179,6 +180,29 @@ test('run command with array of tenants works', function () {
|
||||||
->expectsOutput('Tenant: ' . $tenantId2);
|
->expectsOutput('Tenant: ' . $tenantId2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('run command works when sub command asks questions and accepts arguments', function () {
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
$id = $tenant->getTenantKey();
|
||||||
|
|
||||||
|
Artisan::call('tenants:migrate');
|
||||||
|
|
||||||
|
pest()->artisan("tenants:run --tenants=$id 'user:addwithname Abrar' ")
|
||||||
|
->expectsQuestion('What is your email?', 'email@localhost')
|
||||||
|
->expectsOutput("Tenant: $id")
|
||||||
|
->expectsOutput("User created: Abrar(email@localhost)");
|
||||||
|
|
||||||
|
// Assert we are in central context
|
||||||
|
expect(tenancy()->initialized)->toBeFalse();
|
||||||
|
|
||||||
|
// Assert user was created in tenant context
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
$user = User::first();
|
||||||
|
|
||||||
|
// Assert user is same as provided using the command
|
||||||
|
expect($user->name)->toBe('Abrar');
|
||||||
|
expect($user->email)->toBe('email@localhost');
|
||||||
|
});
|
||||||
|
|
||||||
// todo@tests
|
// todo@tests
|
||||||
function runCommandWorks(): void
|
function runCommandWorks(): void
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests\Etc;
|
namespace Stancl\Tenancy\Tests\Etc\Console;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Stancl\Tenancy\Concerns\HasATenantsOption;
|
use Stancl\Tenancy\Concerns\HasATenantsOption;
|
||||||
use Stancl\Tenancy\Concerns\TenantAwareCommand;
|
use Stancl\Tenancy\Concerns\TenantAwareCommand;
|
||||||
|
use Stancl\Tenancy\Tests\Etc\User;
|
||||||
|
|
||||||
class AddUserCommand extends Command
|
class AddUserCommand extends Command
|
||||||
{
|
{
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests\Etc;
|
namespace Stancl\Tenancy\Tests\Etc\Console;
|
||||||
|
|
||||||
use Orchestra\Testbench\Foundation\Console\Kernel;
|
use Orchestra\Testbench\Foundation\Console\Kernel;
|
||||||
|
|
||||||
|
|
@ -10,6 +10,7 @@ class ConsoleKernel extends Kernel
|
||||||
{
|
{
|
||||||
protected $commands = [
|
protected $commands = [
|
||||||
ExampleCommand::class,
|
ExampleCommand::class,
|
||||||
|
ExampleQuestionCommand::class,
|
||||||
AddUserCommand::class,
|
AddUserCommand::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests\Etc;
|
namespace Stancl\Tenancy\Tests\Etc\Console;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
46
tests/Etc/Console/ExampleQuestionCommand.php
Normal file
46
tests/Etc/Console/ExampleQuestionCommand.php
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Tests\Etc\Console;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Stancl\Tenancy\Tests\Etc\User;
|
||||||
|
|
||||||
|
class ExampleQuestionCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'user:addwithname {name}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Command description';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$email = $this->ask('What is your email?');
|
||||||
|
|
||||||
|
User::create([
|
||||||
|
'name' => $this->argument('name'),
|
||||||
|
'email' => $email,
|
||||||
|
'email_verified_at' => now(),
|
||||||
|
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
||||||
|
'remember_token' => Str::random(10),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->line("User created: ". $this->argument('name') . "($email)");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,11 @@ beforeEach(function () {
|
||||||
], function () {
|
], function () {
|
||||||
Route::get('/foo/{a}/{b}', function ($a, $b) {
|
Route::get('/foo/{a}/{b}', function ($a, $b) {
|
||||||
return "$a + $b";
|
return "$a + $b";
|
||||||
});
|
})->name('foo');
|
||||||
|
|
||||||
|
Route::get('/baz/{a}/{b}', function ($a, $b) {
|
||||||
|
return "$a - $b";
|
||||||
|
})->name('baz');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -123,3 +127,23 @@ test('tenant parameter name can be customized', function () {
|
||||||
->withoutExceptionHandling()
|
->withoutExceptionHandling()
|
||||||
->get('/acme/foo/abc/xyz');
|
->get('/acme/foo/abc/xyz');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('tenant parameter is set for all routes as the default parameter once the tenancy initialized', function () {
|
||||||
|
Tenant::create([
|
||||||
|
'id' => 'acme',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(tenancy()->initialized)->toBeFalse();
|
||||||
|
|
||||||
|
// make a request that will initialize tenancy
|
||||||
|
pest()->get(route('foo', ['tenant' => 'acme', 'a' => 1, 'b' => 2]));
|
||||||
|
|
||||||
|
expect(tenancy()->initialized)->toBeTrue();
|
||||||
|
expect(tenant('id'))->toBe('acme');
|
||||||
|
|
||||||
|
// assert that the route WITHOUT the tenant parameter matches the route WITH the tenant parameter
|
||||||
|
expect(route('baz', ['a' => 1, 'b' => 2]))->toBe(route('baz', ['tenant' => 'acme', 'a' => 1, 'b' => 2]));
|
||||||
|
|
||||||
|
expect(route('baz', ['a' => 1, 'b' => 2]))->toBe('http://localhost/acme/baz/1/2'); // assert the full route string
|
||||||
|
pest()->get(route('baz', ['a' => 1, 'b' => 2]))->assertOk(); // Assert route don't need tenant parameter
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,27 @@ declare(strict_types=1);
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Carbon\CarbonInterval;
|
use Carbon\CarbonInterval;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Auth\TokenGuard;
|
||||||
use Illuminate\Auth\SessionGuard;
|
use Illuminate\Auth\SessionGuard;
|
||||||
|
use Stancl\JobPipeline\JobPipeline;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||||
use Illuminate\Support\Facades\Event;
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Stancl\JobPipeline\JobPipeline;
|
|
||||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
|
||||||
use Stancl\Tenancy\Database\Models\ImpersonationToken;
|
|
||||||
use Stancl\Tenancy\Events\TenancyEnded;
|
use Stancl\Tenancy\Events\TenancyEnded;
|
||||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
|
||||||
use Stancl\Tenancy\Events\TenantCreated;
|
|
||||||
use Stancl\Tenancy\Features\UserImpersonation;
|
|
||||||
use Stancl\Tenancy\Jobs\CreateDatabase;
|
use Stancl\Tenancy\Jobs\CreateDatabase;
|
||||||
|
use Stancl\Tenancy\Events\TenantCreated;
|
||||||
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
|
use Stancl\Tenancy\Features\UserImpersonation;
|
||||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
|
||||||
use Illuminate\Foundation\Auth\User as Authenticable;
|
use Illuminate\Foundation\Auth\User as Authenticable;
|
||||||
|
use Stancl\Tenancy\Database\Models\ImpersonationToken;
|
||||||
|
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||||
|
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Exceptions\StatefulGuardRequiredException;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
pest()->artisan('migrate', [
|
pest()->artisan('migrate', [
|
||||||
|
|
@ -223,6 +225,46 @@ test('impersonation works with multiple models and guards', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('impersonation tokens can be created only with stateful guards', function () {
|
||||||
|
config([
|
||||||
|
'auth.guards' => [
|
||||||
|
'nonstateful' => [
|
||||||
|
'driver' => 'nonstateful',
|
||||||
|
'provider' => 'provider',
|
||||||
|
],
|
||||||
|
'stateful' => [
|
||||||
|
'driver' => 'session',
|
||||||
|
'provider' => 'provider',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'auth.providers.provider' => [
|
||||||
|
'driver' => 'eloquent',
|
||||||
|
'model' => ImpersonationUser::class,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
migrateTenants();
|
||||||
|
|
||||||
|
$user = $tenant->run(function () {
|
||||||
|
return ImpersonationUser::create([
|
||||||
|
'name' => 'Joe',
|
||||||
|
'email' => 'joe@local',
|
||||||
|
'password' => bcrypt('secret'),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
Auth::extend('nonstateful', fn($app, $name, array $config) => new TokenGuard(Auth::createUserProvider($config['provider']), request()));
|
||||||
|
|
||||||
|
expect(fn() => tenancy()->impersonate($tenant, $user->id, '/dashboard', 'nonstateful'))
|
||||||
|
->toThrow(StatefulGuardRequiredException::class);
|
||||||
|
|
||||||
|
Auth::extend('stateful', fn ($app, $name, array $config) => new SessionGuard($name, Auth::createUserProvider($config['provider']), session()));
|
||||||
|
|
||||||
|
expect(tenancy()->impersonate($tenant, $user->id, '/dashboard', 'stateful'))
|
||||||
|
->toBeInstanceOf(ImpersonationToken::class);
|
||||||
|
});
|
||||||
|
|
||||||
function migrateTenants()
|
function migrateTenants()
|
||||||
{
|
{
|
||||||
pest()->artisan('tenants:migrate')->assertExitCode(0);
|
pest()->artisan('tenants:migrate')->assertExitCode(0);
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||||
'--realpath' => true,
|
'--realpath' => true,
|
||||||
'--force' => true,
|
'--force' => true,
|
||||||
],
|
],
|
||||||
'tenancy.bootstrappers.redis' => RedisTenancyBootstrapper::class,
|
'tenancy.bootstrappers.redis' => RedisTenancyBootstrapper::class, // todo0 change this to []? two tests in TenantDatabaseManagerTest are failing with that
|
||||||
'queue.connections.central' => [
|
'queue.connections.central' => [
|
||||||
'driver' => 'sync',
|
'driver' => 'sync',
|
||||||
'central' => true,
|
'central' => true,
|
||||||
|
|
@ -150,7 +150,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||||
*/
|
*/
|
||||||
protected function resolveApplicationConsoleKernel($app)
|
protected function resolveApplicationConsoleKernel($app)
|
||||||
{
|
{
|
||||||
$app->singleton('Illuminate\Contracts\Console\Kernel', Etc\ConsoleKernel::class);
|
$app->singleton('Illuminate\Contracts\Console\Kernel', Etc\Console\ConsoleKernel::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function randomString(int $length = 10)
|
public function randomString(int $length = 10)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue