From 2a39b0526a1fb122d6965e7932ec62399b1d5b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Thu, 10 Nov 2022 16:44:52 +0100 Subject: [PATCH] Fix #998, properly replace ALL tenant_id literals --- CONTRIBUTING.md | 8 +++++++- assets/config.php | 2 +- ...create_tenant_user_impersonation_tokens_table.php | 5 +++-- .../2019_09_15_000020_create_domains_table.php | 5 +++-- src/Database/Concerns/BelongsToTenant.php | 12 ++++-------- src/Database/Concerns/HasDomains.php | 3 ++- src/Database/Concerns/HasScopedValidationRules.php | 5 +++-- src/Database/TenantScope.php | 4 ++-- src/Features/UserImpersonation.php | 4 ++-- src/Listeners/UpdateSyncedResource.php | 3 ++- src/Tenancy.php | 6 ++++++ 11 files changed, 35 insertions(+), 22 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 03aa4ee8..0095be7e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,10 +10,16 @@ Run `composer docker-up` to start the containers. Then run `composer test` to ru If you need to pass additional flags to phpunit, use `./test --foo` instead of `composer test --foo`. Composer scripts unfortunately don't pass CLI arguments. -If you want to run a specific test (or test file), you can also use `./t 'name of the test'`. This is equivalent to `./test --no-coverage --filter 'name of the test'`. +If you want to run a specific test (or test file), you can also use `./t 'name of the test'`. This is equivalent to `./test --no-coverage --filter 'name of the test'` (`--no-coverage` speeds up the execution time). When you're done testing, run `composer docker-down` to shut down the containers. +### Debugging tests + +If you're developing some feature and you encounter `SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry` errors, it's likely that some PHP errors were thrown in past test runs and prevented the test cleanup from running properly. + +To fix this, simply delete the database memory by shutting down containers and starting them again: `composer docker-down && composer docker-up`. + ### Docker on M1 Run `composer docker-m1` to symlink `docker-compose-m1.override.yml` to `docker-compose.override.yml`. This will reconfigure a few services in the docker compose config to be compatible with M1. diff --git a/assets/config.php b/assets/config.php index ce276f3d..3778e107 100644 --- a/assets/config.php +++ b/assets/config.php @@ -14,7 +14,7 @@ return [ 'domain' => Stancl\Tenancy\Database\Models\Domain::class, /** - * Name of the column used to for ->tenant() relationships. + * Name of the column used to relate models to tenants. * * This is used by the HasDomains trait, and models that use the BelongsToTenant trait (used in single-database tenancy). */ diff --git a/assets/impersonation-migrations/2020_05_15_000010_create_tenant_user_impersonation_tokens_table.php b/assets/impersonation-migrations/2020_05_15_000010_create_tenant_user_impersonation_tokens_table.php index 32597f38..7bcc3e75 100644 --- a/assets/impersonation-migrations/2020_05_15_000010_create_tenant_user_impersonation_tokens_table.php +++ b/assets/impersonation-migrations/2020_05_15_000010_create_tenant_user_impersonation_tokens_table.php @@ -5,6 +5,7 @@ declare(strict_types=1); use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +use Stancl\Tenancy\Tenancy; class CreateTenantUserImpersonationTokensTable extends Migration { @@ -17,13 +18,13 @@ class CreateTenantUserImpersonationTokensTable extends Migration { Schema::create('tenant_user_impersonation_tokens', function (Blueprint $table) { $table->string('token', 128)->primary(); - $table->string('tenant_id'); + $table->string(Tenancy::tenantKeyColumn()); $table->string('user_id'); $table->string('auth_guard'); $table->string('redirect_url'); $table->timestamp('created_at'); - $table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade'); + $table->foreign(Tenancy::tenantKeyColumn())->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade'); }); } diff --git a/assets/migrations/2019_09_15_000020_create_domains_table.php b/assets/migrations/2019_09_15_000020_create_domains_table.php index 17f706c2..511e6cc9 100644 --- a/assets/migrations/2019_09_15_000020_create_domains_table.php +++ b/assets/migrations/2019_09_15_000020_create_domains_table.php @@ -5,6 +5,7 @@ declare(strict_types=1); use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +use Stancl\Tenancy\Tenancy; class CreateDomainsTable extends Migration { @@ -18,10 +19,10 @@ class CreateDomainsTable extends Migration Schema::create('domains', function (Blueprint $table) { $table->increments('id'); $table->string('domain', 255)->unique(); - $table->string('tenant_id'); + $table->string(Tenancy::tenantKeyColumn()); $table->timestamps(); - $table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade'); + $table->foreign(Tenancy::tenantKeyColumn())->references('id')->on('tenants')->onUpdate('cascade'); }); } diff --git a/src/Database/Concerns/BelongsToTenant.php b/src/Database/Concerns/BelongsToTenant.php index 1be3c4cf..ccf87c81 100644 --- a/src/Database/Concerns/BelongsToTenant.php +++ b/src/Database/Concerns/BelongsToTenant.php @@ -6,6 +6,7 @@ namespace Stancl\Tenancy\Database\Concerns; use Stancl\Tenancy\Contracts\Tenant; use Stancl\Tenancy\Database\TenantScope; +use Stancl\Tenancy\Tenancy; /** * @property-read Tenant $tenant @@ -14,12 +15,7 @@ trait BelongsToTenant { public function tenant() { - return $this->belongsTo(config('tenancy.models.tenant'), static::tenantIdColumn()); - } - - public static function tenantIdColumn(): string - { - return config('tenancy.models.tenant_key_column'); + return $this->belongsTo(config('tenancy.models.tenant'), Tenancy::tenantKeyColumn()); } public static function bootBelongsToTenant(): void @@ -27,9 +23,9 @@ trait BelongsToTenant static::addGlobalScope(new TenantScope); static::creating(function ($model) { - if (! $model->getAttribute(static::tenantIdColumn()) && ! $model->relationLoaded('tenant')) { + if (! $model->getAttribute(Tenancy::tenantKeyColumn()) && ! $model->relationLoaded('tenant')) { if (tenancy()->initialized) { - $model->setAttribute(static::tenantIdColumn(), tenant()->getTenantKey()); + $model->setAttribute(Tenancy::tenantKeyColumn(), tenant()->getTenantKey()); $model->setRelation('tenant', tenant()); } } diff --git a/src/Database/Concerns/HasDomains.php b/src/Database/Concerns/HasDomains.php index aa1e49d0..ae3aed42 100644 --- a/src/Database/Concerns/HasDomains.php +++ b/src/Database/Concerns/HasDomains.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Stancl\Tenancy\Database\Concerns; use Stancl\Tenancy\Contracts\Domain; +use Stancl\Tenancy\Tenancy; /** * @property-read Domain[]|\Illuminate\Database\Eloquent\Collection $domains @@ -15,7 +16,7 @@ trait HasDomains { public function domains() { - return $this->hasMany(config('tenancy.models.domain'), 'tenant_id'); + return $this->hasMany(config('tenancy.models.domain'), Tenancy::tenantKeyColumn()); } public function createDomain($data): Domain diff --git a/src/Database/Concerns/HasScopedValidationRules.php b/src/Database/Concerns/HasScopedValidationRules.php index 7913a215..979a3ecc 100644 --- a/src/Database/Concerns/HasScopedValidationRules.php +++ b/src/Database/Concerns/HasScopedValidationRules.php @@ -6,16 +6,17 @@ namespace Stancl\Tenancy\Database\Concerns; use Illuminate\Validation\Rules\Exists; use Illuminate\Validation\Rules\Unique; +use Stancl\Tenancy\Tenancy; trait HasScopedValidationRules { public function unique($table, $column = 'NULL') { - return (new Unique($table, $column))->where(BelongsToTenant::tenantIdColumn(), $this->getTenantKey()); + return (new Unique($table, $column))->where(Tenancy::tenantKeyColumn(), $this->getTenantKey()); } public function exists($table, $column = 'NULL') { - return (new Exists($table, $column))->where(BelongsToTenant::tenantIdColumn(), $this->getTenantKey()); + return (new Exists($table, $column))->where(Tenancy::tenantKeyColumn(), $this->getTenantKey()); } } diff --git a/src/Database/TenantScope.php b/src/Database/TenantScope.php index fdab9d70..e3b1db69 100644 --- a/src/Database/TenantScope.php +++ b/src/Database/TenantScope.php @@ -7,7 +7,7 @@ namespace Stancl\Tenancy\Database; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; -use Stancl\Tenancy\Database\Concerns\BelongsToTenant; +use Stancl\Tenancy\Tenancy; class TenantScope implements Scope { @@ -17,7 +17,7 @@ class TenantScope implements Scope return; } - $builder->where($model->qualifyColumn(BelongsToTenant::tenantIdColumn()), tenant()->getTenantKey()); + $builder->where($model->qualifyColumn(Tenancy::tenantKeyColumn()), tenant()->getTenantKey()); } public function extend(Builder $builder): void diff --git a/src/Features/UserImpersonation.php b/src/Features/UserImpersonation.php index 41bf774b..4c9bb104 100644 --- a/src/Features/UserImpersonation.php +++ b/src/Features/UserImpersonation.php @@ -20,7 +20,7 @@ class UserImpersonation implements Feature { $tenancy->macro('impersonate', function (Tenant $tenant, string $userId, string $redirectUrl, string $authGuard = null): ImpersonationToken { return ImpersonationToken::create([ - 'tenant_id' => $tenant->getTenantKey(), + Tenancy::tenantKeyColumn() => $tenant->getTenantKey(), 'user_id' => $userId, 'redirect_url' => $redirectUrl, 'auth_guard' => $authGuard, @@ -39,7 +39,7 @@ class UserImpersonation implements Feature abort_if($tokenExpired, 403); - $tokenTenantId = (string) $token->tenant_id; + $tokenTenantId = (string) $token->getAttribute(Tenancy::tenantKeyColumn()); $currentTenantId = (string) tenant()->getTenantKey(); abort_unless($tokenTenantId === $currentTenantId, 403); diff --git a/src/Listeners/UpdateSyncedResource.php b/src/Listeners/UpdateSyncedResource.php index 39391eac..38245a80 100644 --- a/src/Listeners/UpdateSyncedResource.php +++ b/src/Listeners/UpdateSyncedResource.php @@ -14,6 +14,7 @@ use Stancl\Tenancy\Database\TenantCollection; use Stancl\Tenancy\Events\SyncedResourceChangedInForeignDatabase; use Stancl\Tenancy\Events\SyncedResourceSaved; use Stancl\Tenancy\Exceptions\ModelNotSyncMasterException; +use Stancl\Tenancy\Tenancy; // todo@v4 review all code related to resource syncing @@ -77,7 +78,7 @@ class UpdateSyncedResource extends QueueableListener /** @var Tenant */ $tenant = $event->tenant; - return ((string) $model->pivot->tenant_id) === ((string) $tenant->getTenantKey()); + return ((string) $model->pivot->getAttribute(Tenancy::tenantKeyColumn())) === ((string) $tenant->getTenantKey()); }; $mappingExists = $centralModel->tenants->contains($currentTenantMapping); diff --git a/src/Tenancy.php b/src/Tenancy.php index 8788ec4c..e8187dd8 100644 --- a/src/Tenancy.php +++ b/src/Tenancy.php @@ -105,6 +105,12 @@ class Tenancy return $model; } + /** Name of the column used to relate models to tenants. */ + public static function tenantKeyColumn(): string + { + return config('tenancy.models.tenant_key_column') ?? 'tenant_id'; + } + /** * Try to find a tenant using an ID. *