mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 15:54:03 +00:00
[4.x] Make impersonation tokens require stateful guards (#935)
* Throw an exception on attempt to create impersonation token with a non-stateful guard * Test that impersonation tokens can only be created with a stateful guard * Fix code style (php-cs-fixer) * Escape backslashes in the exception's message Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com> * Make the exception only about requiring a stateful guard Co-authored-by: PHP CS Fixer <phpcsfixer@example.com> Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>
This commit is contained in:
parent
f83504ac6f
commit
3bf2c39e1a
3 changed files with 77 additions and 11 deletions
|
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue