1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-02-05 04:34:03 +00:00

request data identification: add tenant_model_column configuration

This commit is contained in:
Samuel Štancl 2025-05-30 03:55:52 +02:00
parent 16171b1eaa
commit ec9b585e70
5 changed files with 61 additions and 20 deletions

View file

@ -132,6 +132,8 @@ return [
'cookie' => 'tenant', 'cookie' => 'tenant',
'query_parameter' => 'tenant', 'query_parameter' => 'tenant',
'tenant_model_column' => null, // null = tenant key
'cache' => false, 'cache' => false,
'cache_ttl' => 3600, // seconds 'cache_ttl' => 3600, // seconds
'cache_store' => null, // null = default 'cache_store' => null, // null = default

View file

@ -176,6 +176,7 @@ class TenancyUrlGenerator extends UrlGenerator
{ {
if (tenant() && static::$passTenantParameterToRoutes) { if (tenant() && static::$passTenantParameterToRoutes) {
if (static::$defaultParameterNames) { if (static::$defaultParameterNames) {
// todo0 this should be changed to something like static::$queryParameter and it should respect the configured tenant model column
return array_merge($parameters, ['tenant' => tenant()->getTenantKey()]); return array_merge($parameters, ['tenant' => tenant()->getTenantKey()]);
} else { } else {
return array_merge($parameters, [PathTenantResolver::tenantParameterName() => PathTenantResolver::tenantParameterValue(tenant())]); return array_merge($parameters, [PathTenantResolver::tenantParameterName() => PathTenantResolver::tenantParameterValue(tenant())]);

View file

@ -20,7 +20,9 @@ class RequestDataTenantResolver extends Contracts\CachedTenantResolver
{ {
$payload = (string) $args[0]; $payload = (string) $args[0];
if ($payload && $tenant = tenancy()->find($payload, withRelations: true)) { $column = static::tenantModelColumn();
if ($payload && $tenant = tenancy()->find($payload, $column, withRelations: true)) {
return $tenant; return $tenant;
} }
@ -34,6 +36,11 @@ class RequestDataTenantResolver extends Contracts\CachedTenantResolver
]; ];
} }
public static function tenantModelColumn(): string
{
return config('tenancy.identification.resolvers.' . static::class . '.tenant_model_column') ?? tenancy()->model()->getTenantKeyName();
}
/** /**
* Returns the name of the header used for identification, or null if header identification is disabled. * Returns the name of the header used for identification, or null if header identification is disabled.
*/ */

View file

@ -119,7 +119,7 @@ test('request data identification route helper behavior', function (bool $addTen
tenancy()->initialize($tenant); tenancy()->initialize($tenant);
// todo0 test changing tenancy.identification.resolvers.<request data>.query_parameter // todo0 test changing tenancy.identification.resolvers.<request data>.query_parameter and tenant_model_column
if ($passTenantParameterToRoutes) { if ($passTenantParameterToRoutes) {
expect(route('tenant.home'))->toBe("{$appUrl}/tenant/home?tenant={$tenantKey}"); expect(route('tenant.home'))->toBe("{$appUrl}/tenant/home?tenant={$tenantKey}");

View file

@ -2,6 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData; use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
@ -21,50 +22,80 @@ beforeEach(function () {
}); });
}); });
test('header identification works', function () { test('header identification works', function (string|null $tenantModelColumn) {
$tenant = Tenant::create(); if ($tenantModelColumn) {
Schema::table('tenants', function (Blueprint $table) use ($tenantModelColumn) {
$table->string($tenantModelColumn)->unique();
});
Tenant::$extraCustomColumns = [$tenantModelColumn];
}
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.tenant_model_column' => $tenantModelColumn]);
$tenant = Tenant::create($tenantModelColumn ? [$tenantModelColumn => 'acme'] : []);
$payload = $tenantModelColumn ? 'acme' : $tenant->id;
// Default header name // Default header name
$this->withoutExceptionHandling()->withHeader('X-Tenant', $tenant->id)->get('test')->assertSee($tenant->id); $this->withoutExceptionHandling()->withHeader('X-Tenant', $payload)->get('test')->assertSee($tenant->id);
// Custom header name // Custom header name
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.header' => 'X-Custom-Tenant']); config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.header' => 'X-Custom-Tenant']);
$this->withoutExceptionHandling()->withHeader('X-Custom-Tenant', $tenant->id)->get('test')->assertSee($tenant->id); $this->withoutExceptionHandling()->withHeader('X-Custom-Tenant', $payload)->get('test')->assertSee($tenant->id);
// Setting the header to null disables header identification // Setting the header to null disables header identification
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.header' => null]); config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.header' => null]);
expect(fn () => $this->withoutExceptionHandling()->withHeader('X-Tenant', $tenant->id)->get('test'))->toThrow(TenantCouldNotBeIdentifiedByRequestDataException::class); expect(fn () => $this->withoutExceptionHandling()->withHeader('X-Tenant', $payload)->get('test'))->toThrow(TenantCouldNotBeIdentifiedByRequestDataException::class);
}); })->with([null, 'slug']);
test('query parameter identification works', function () { test('query parameter identification works', function (string|null $tenantModelColumn) {
$tenant = Tenant::create(); if ($tenantModelColumn) {
Schema::table('tenants', function (Blueprint $table) use ($tenantModelColumn) {
$table->string($tenantModelColumn)->unique();
});
Tenant::$extraCustomColumns = [$tenantModelColumn];
}
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.tenant_model_column' => $tenantModelColumn]);
$tenant = Tenant::create($tenantModelColumn ? [$tenantModelColumn => 'acme'] : []);
$payload = $tenantModelColumn ? 'acme' : $tenant->id;
// Default query parameter name // Default query parameter name
$this->withoutExceptionHandling()->get('test?tenant=' . $tenant->id)->assertSee($tenant->id); $this->withoutExceptionHandling()->get('test?tenant=' . $payload)->assertSee($tenant->id);
// Custom query parameter name // Custom query parameter name
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.query_parameter' => 'custom_tenant']); config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.query_parameter' => 'custom_tenant']);
$this->withoutExceptionHandling()->get('test?custom_tenant=' . $tenant->id)->assertSee($tenant->id); $this->withoutExceptionHandling()->get('test?custom_tenant=' . $payload)->assertSee($tenant->id);
// Setting the query parameter to null disables query parameter identification // Setting the query parameter to null disables query parameter identification
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.query_parameter' => null]); config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.query_parameter' => null]);
expect(fn () => $this->withoutExceptionHandling()->get('test?tenant=' . $tenant->id))->toThrow(TenantCouldNotBeIdentifiedByRequestDataException::class); expect(fn () => $this->withoutExceptionHandling()->get('test?tenant=' . $payload))->toThrow(TenantCouldNotBeIdentifiedByRequestDataException::class);
}); })->with([null, 'slug']);
test('cookie identification works', function () { test('cookie identification works', function (string|null $tenantModelColumn) {
$tenant = Tenant::create(); if ($tenantModelColumn) {
Schema::table('tenants', function (Blueprint $table) use ($tenantModelColumn) {
$table->string($tenantModelColumn)->unique();
});
Tenant::$extraCustomColumns = [$tenantModelColumn];
}
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.tenant_model_column' => $tenantModelColumn]);
$tenant = Tenant::create($tenantModelColumn ? [$tenantModelColumn => 'acme'] : []);
$payload = $tenantModelColumn ? 'acme' : $tenant->id;
// Default cookie name // Default cookie name
$this->withoutExceptionHandling()->withUnencryptedCookie('tenant', $tenant->id)->get('test')->assertSee($tenant->id); $this->withoutExceptionHandling()->withUnencryptedCookie('tenant', $payload)->get('test')->assertSee($tenant->id);
// Custom cookie name // Custom cookie name
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.cookie' => 'custom_tenant_id']); config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.cookie' => 'custom_tenant_id']);
$this->withoutExceptionHandling()->withUnencryptedCookie('custom_tenant_id', $tenant->id)->get('test')->assertSee($tenant->id); $this->withoutExceptionHandling()->withUnencryptedCookie('custom_tenant_id', $payload)->get('test')->assertSee($tenant->id);
// Setting the cookie to null disables cookie identification // Setting the cookie to null disables cookie identification
config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.cookie' => null]); config(['tenancy.identification.resolvers.' . RequestDataTenantResolver::class . '.cookie' => null]);
expect(fn () => $this->withoutExceptionHandling()->withUnencryptedCookie('tenant', $tenant->id)->get('test'))->toThrow(TenantCouldNotBeIdentifiedByRequestDataException::class); expect(fn () => $this->withoutExceptionHandling()->withUnencryptedCookie('tenant', $payload)->get('test'))->toThrow(TenantCouldNotBeIdentifiedByRequestDataException::class);
}); })->with([null, 'slug']);
// todo@tests encrypted cookie // todo@tests encrypted cookie