1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-05-06 18:04:03 +00:00
Commit graph

509 commits

Author SHA1 Message Date
lukinovec
b03895de47 Merge branch 'validate-sql-parameters' into boilerplate-temp 2026-05-04 13:41:57 +02:00
lukinovec
03318752b6 Specify charset and collation config in test 2026-05-04 13:40:42 +02:00
lukinovec
66ae88a325 Fix non-string parameter validation assertion 2026-05-04 13:26:01 +02:00
lukinovec
90608e5a0b Merge branch 'validate-sql-parameters' into boilerplate-temp 2026-05-04 13:07:25 +02:00
lukinovec
e59195eefe Improve coverage
Cover non-string parameter validation and in-memory DB name validation
2026-05-04 13:04:57 +02:00
lukinovec
bdbfbd4561 Remove extra variable 2026-05-04 12:32:25 +02:00
lukinovec
de913486e0 Specify exception message in assertions 2026-05-04 12:27:46 +02:00
lukinovec
338526d9fb Query for MySQL defaults instead of assuming them in charset test 2026-05-04 11:54:45 +02:00
lukinovec
405aaafb4e Handle MySQL charset and collation
Make createDatabase execute CREATE DATABASE without passing charset and collation so that if these parameters are null, the MySQL server's defaults will be used. Only add charset and collation to the statement if they're not null.
2026-05-04 11:15:51 +02:00
lukinovec
26c161a940 Add regression test for makeConnectionConfig not working correctly with custom $path
In makeConnectionConfig, changing the $this->getPath($databaseName) line back to `$baseConfig['database'] = database_path($databaseName);` will make the added test fail.
2026-05-01 15:16:54 +02:00
lukinovec
7f93f4460a Test that the SQLite DB manager recognizes in-memory DBs 2026-05-01 14:35:18 +02:00
lukinovec
7363318f6e Make in-memory DB detection more strict
In-memory DBs have to start with "file:_tenancy_inmemory_". This prevents path traversal.
2026-05-01 13:09:37 +02:00
lukinovec
b1f0d0a43c
Get central DB from config in harden test
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-05-01 12:34:28 +02:00
lukinovec
2ae1f79d50 Cover empty string parameters 2026-05-01 12:32:03 +02:00
lukinovec
0ce3d863ce DATABASE_URL test: set config for both datasets 2026-05-01 12:11:00 +02:00
lukinovec
52f6857302 If harden throws an exception, revert connection back to central 2026-05-01 12:08:02 +02:00
lukinovec
f5f5f1d4aa Fix DB bootstrapper test
"database tenancy bootstrapper throws an exception if DATABASE_URL is set" was failing with the null $databaseUrl because the tenant DB was never created. This test was ignored during test runs because the test file lacked the 'Test' suffix.
2026-05-01 11:53:27 +02:00
lukinovec
fbd1e02564 Correct DatabaseTenancyBootstrapper test filename
DatabaseTenancyBootstrapper is ignored by ./t, it should be suffixed with 'Test'.
2026-05-01 11:50:01 +02:00
lukinovec
665404e7fa Add DatabaseTenancyBootstrapper::$harden
Since It's possible to update tenant's db_name to the central DB or the DB of another tenant. Setting $harden to true prevents tenants from connecting to the wrong databases.
2026-05-01 11:44:56 +02:00
lukinovec
76c324d758 Add validateFilename()
Use validateFilename instead of validateParameter in SQLiteDatabaseManager. Directories are no longer considered valid SQLite database names.
2026-05-01 09:03:50 +02:00
lukinovec
bacbf934e1 Improve validation exception message 2026-04-30 14:52:53 +02:00
lukinovec
50ea524ad2 Simplify test, improve comments 2026-04-30 11:16:39 +02:00
lukinovec
4bdb877ca4 Cover null parameter skipping
Also cover that in-memory db names aren't validated in databaseExists
2026-04-30 10:45:29 +02:00
lukinovec
322257f456 Validate SQLite filename in databaseExists
Add validation so that a malicious tenant DB name can't be used to detect if a file exists.
2026-04-30 09:49:03 +02:00
lukinovec
4a3e6bae00 Test invalid passwords, improve test name and comments 2026-04-29 17:35:11 +02:00
lukinovec
db03997339 Validate SQLite DB names in create/deleteDatabase()
Also stop skipping the validation test for sqlite.
2026-04-29 17:35:11 +02:00
lukinovec
5adbc14a7e Test SQL parameter validation
WIP: password validation, SQLite (check if validation is enough for valid FS paths), revisit the test
2026-04-29 14:16:00 +02:00
lukinovec
2dfbbef0f3 Merge branch 'pending-improvements' into boilerplate-dev 2026-04-28 14:55:44 +02:00
lukinovec
e81e6ac651 Add regression test for jobs recognizing pending tenants
Reverting the HasPending changes (in createPending(), create tenant without pending_since, and only after that, update the tenant to give it pending_since) makes the test fail.
2026-04-28 12:47:42 +02:00
lukinovec
0bb112dbdf Cover invalid --with-pending values in tests
Include --with-pending=foo as the fasly option in the falsy --with-pending test.
2026-04-28 12:19:09 +02:00
lukinovec
f309dcc65c Test pending tenant creation with job pipelines
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 10:59:04 +02:00
lukinovec
6b4d22bb92 Update --with-pending tests
Cover that --with-pending can now accept values, and that the option takes precedence over include_in_queries config. Also simplify/improve existing assertions.
2026-04-28 10:39:42 +02:00
lukinovec
e8608f1d05 Merge branch 'master' into boilerplate-dev 2026-04-24 09:51:40 +02:00
lukinovec
984911946a
Change tenant storage listeners into jobs (#1446)
The `CreateTenantStorage` and `DeleteTenantStorage` listeners were used
alongside JobPipelines. When the `TenantCreated` JobPipeline had
`shouldBeQueued(true)` and the `Listeners\CreateTenantStorage` was
uncommented, the listener would throw an exception
(`Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException
Database tenantX.sqlite does not exist.`) because at the time of
executing the listener, the tenant DB wasn't created yet.

The same issue could likely also occur in the `DeleteTenantStorage`
listener as it uses `tenancy()->run()` to resolve the tenant's storage
path which wouldn't work if the tenant's database (or other resources)
was already deleted, making initialization impossible.

This PR changes `DeleteTenantStorage` into a job and puts it (commented)
into the job pipeline, so that it can be queued with the rest of the
jobs. It also removes `CreateTenantStorage` because it should be
redundant with the FilesystemTenancyBootstrapper creating the same paths
automatically when storage path is suffixed.

The old classes are kept but deprecated for backwards compatibility.

We've also added some edge case hardening to `DeleteTenantStorage` to
make sure it never deletes the central storage path directory, which
previously could in theory occur due to a misconfiguration if a user
enabled this job/listener but disabled storage path suffixing.

Co-authored-by: Samuel Štancl <samuel@archte.ch>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-22 16:45:54 +02:00
lukinovec
5d047089ea Merge branch 'master' into boilerplate-dev 2026-04-22 14:47:28 +02:00
lukinovec
ab2a4d8438
Fix chaining withoutPending() with where() (#1457)
At the moment, `where()` cannot be used correctly while using
`withoutPending()`. For example, if we have a single non-pending tenant
in our DB (with ID 'foo'), queries like
`Tenant::withoutPending()->where('id', 'nonexistent')->first()`will
incorrectly return the non-pending tenant ('foo').

This is because `withoutPending()` does
`$builder->whereNull('data->pending_since')->orWhereNull('data')`. These
two aren't grouped, so `withoutPending()->where('id', 'nonexistent')`
basically translates to "WHERE data->pending_since IS NULL **OR (data IS
NULL AND id = 'nonexistent')**". So the query will include all tenants
whose `pending_since` is null (= all non-pending tenants).

Grouping `->whereNull('data->pending_since')->orWhereNull('data')` in a
closure passed to a separate `where()` fixes this issue.
2026-04-22 14:32:53 +02:00
60dd5226c4
[4.x] Add Tenancy::reinitialize() method (#1449)
Some bootstrappers read attributes of the tenant during bootstrap() but
don't respond to changes made to the tenant afterwards.

Therefore, when making changes to the tenant that'd affect the behavior
of a bootstrapper, it's necessary to reinitialize tenancy (if it matters
that changes are reflected immediately). This adds a convenience helper
for that purpose.
2026-04-08 19:21:43 +02:00
lukinovec
3103ff60c0 Add toRoute() override to TenancyUrlGenerator
Also update `route()` override since `parent::route()` calls `toRoute()` under the hood (similarly to how `parent::temporarySignedRoute()` calls `route()`)
2026-03-19 08:26:47 +01:00
lukinovec
a306f1e972 Test that stopImpersonating() can keep the user authenticated 2026-03-19 08:26:47 +01:00
lukinovec
e4f3cedcd5 Instead of setting the 'tenancy_impersonating' session variable, store auth guard in 'tenancy_impersonation_guard'
Also make `stopImpersonating()` able to keep the user logged in.
2026-03-19 08:26:47 +01:00
c4960b76cb
[4.x] Laravel 13 support (#1443)
- Update ci.yml and composer.json
- Wrap single database tenancy trait scopes in whenBooted()
- Update SessionSeparationTest to use laravel-cache- prefix in L13
  and laravel_cache_ in <=L12. Our own prefix remains tenant_%tenant%_
  (as configured in tenancy.cache.prefix). We could update this to be
  tenant-%tenant%- from now on for consistency with Laravel's prefixes
  (changed in https://github.com/laravel/framework/pull/56172) but I'm
  not sure yet. _ seems to read a bit better but perhaps consistency
  is more important. We may change this later and it can be adjusted
  in userland easily (since it's just a config option).
2026-03-18 19:17:28 +01:00
lukinovec
16861d2599
[4.x] Make URL::temporarySignedRoute() respect the bypass parameter (#1438)
Using `URL::temporarySignedRoute()` in tenant context with
`UrlGeneratorBootstrapper` enabled doesn't work the same as `route()`.
The bypass parameter doesn't actually bypass the route name prefixing.

`route()` is called in the `parent::temporarySignedRoute()` call, and
because the bypass parameter is removed before calling
`parent::temporarySignedRoute()`, the underlying `route()` call doesn't
get the bypass parameter and it ends up attempting to generate URL for a
route with the name prefixed with 'tenant.'.

This PR adds the bypass parameter back after `prepareRouteInputs()`, so
that `parent::temporarySignedRoute()` receives it, and the underlying
`route()` call respects it. Also added basic tests for the
`URL::temporarySignedRoute()` behavior (the new bypass parameter test
works as a regression test).
2026-03-09 02:07:02 +01:00
Victor R
3c0e21b726
[4.x] Filesystem bootstrapper: scoped disk support (#1402)
Fixes #1401

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: lukinovec <lukinovec@gmail.com>
Co-authored-by: Samuel Stancl <samuel@archte.ch>
2025-12-16 23:17:11 +01:00
lukinovec
159e600a9b Syncing: support morph maps in TriggerSyncingEvents 2025-12-12 03:43:52 +01:00
04a20ca930
[MINOR BC BREAK] Syncing: PivotWithRelation -> PivotWithCentralResource
The old names of the class and method were misleading. We don't
actually need any relation. And we don't even need a model instance
as we were returning previously -- the only use of that method was
in TriggerSyncingEvents which would immediately use ::class on the
returned value. Therefore, all we are asking for in this interface
is just the central resource class.
2025-11-26 05:52:55 +01:00
lukinovec
e079803025 Syncing: Add DeleteAllTenantMappings listener 2025-11-26 05:52:55 +01:00
lukinovec
44e8ec8abf Syncing: SyncedResourceDeleted event and DeleteResourceMapping listener
Also move pivot record deletion to that listener and improve tests

The 'tenant pivot records are deleted along with the tenants to which
they belong to' test is failing in this commit -- the listener
for deleting mappings when a *tenant* is deleted is only implemented
in the next commit. The only change done here is to re-add FKs
(necessary for passing *in this commit* in that specific dataset
variant) that were removed from the default test migration as we now
have the DeleteResourceMapping listener that's enabled by default.
2025-11-26 05:52:48 +01:00
6ef4b91744
Cloning: improve type annotations, add cloneRoutes() for convenience 2025-11-10 02:16:57 +01:00
197513dd84
Cloning: addTenantMiddleware() for specifying ID MW for cloned route
Previously, tenant identification middleware was typically specified
for the cloned route by "inheriting" it from the central route, which
necessarily meant that the central route had to also be marked as
universal so it could continue working in the central context --
despite presumably not being usable in the tenant context, thus being
universal for no proper reason. In such cases, universal routes were
used mainly as a mechanism for specifying the tenant identification
middleware to use on the cloned tenant route.

Given that recent refactors of the cloning feature have made it more
customizable and a bit nicer to use "multiple times", i.e. run handle()
with a few different configurations of the action, letting the
developer specify the used tenant middleware using a method like this
only makes sense.

The feature also becomes more independently usable and not just a
"hack for universal routes with path identification".
2025-11-09 00:27:14 +01:00
97c5afd2cf
Cloning: clarify case where neither paths nor domains differ
In such a case, the cloned route will actually *override* the original
route, rather than being unused as the original docblock claimed.

Also adds a static make() function for convenience.
2025-11-08 20:38:01 +01:00