1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-05-07 11:34:04 +00:00
Commit graph

1449 commits

Author SHA1 Message Date
github-actions[bot]
7683befa54 Fix code style (php-cs-fixer) 2026-05-01 12:10:13 +00:00
lukinovec
48b4837905 Validate in-memory db names, move SQLite-specific methods to the SQLiteManager 2026-05-01 14:09:56 +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
github-actions[bot]
fc6a931a32 Fix code style (php-cs-fixer) 2026-05-01 09:50:30 +00: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
1a01164b87 Make validateFilename accept string instead of ?string 2026-05-01 10:46:37 +02:00
lukinovec
2bdda23a56 Disallow empty strings as filenames 2026-05-01 10:37:22 +02:00
github-actions[bot]
f3836cc623 Fix code style (php-cs-fixer) 2026-05-01 07:34:32 +00:00
lukinovec
9611a05f35 Skip null parameters, throw for other non-string parameters 2026-05-01 09:34:11 +02:00
lukinovec
e8168eb0b9 Add string check to validateFilename, swap validation order
Validate characters first, only then throw if the filename is a directory.
2026-05-01 09:16:17 +02:00
lukinovec
d3607f84bf Use 'allowedCharacters' instead of 'allowlist', code quality 2026-05-01 09:11:55 +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
2bd3a868ec Quote database parameter in GRANT statement for consistency
The database name is always quoted in statements (without binding) now.
2026-04-30 16:14:06 +02:00
lukinovec
37a4c7dd27 Check if paremeter is string 2026-04-30 15:08:46 +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
46f73c42ad Improve ValidatesDatabaseParameters comments, delete extra early return 2026-04-30 10:44:36 +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
75b74f2e6c Make validateParameter have void return type 2026-04-30 09:28:48 +02:00
lukinovec
f3f1ab977a
Skip null parameters in validateParameter
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-30 09:15:18 +02:00
lukinovec
85929493d5 Improve ValidatesDatabaseParameters docblocks 2026-04-29 17:35:11 +02:00
lukinovec
740d53e9cc Rename ValidatesSqlParameters to ValidatesDatabaseParameters 2026-04-29 17:35:11 +02:00
lukinovec
4a3e6bae00 Test invalid passwords, improve test name and comments 2026-04-29 17:35:11 +02:00
lukinovec
0fdb8b2041 Validate user passwords in DB managers
Also, make the validateParameter method ignore null parameters, e.g. for cases when tenants are created using Tenant::make() without tenancy_db_username set -- $databaseConfig->getUsername() allows null, same should go for the validate method whose only concern is checking strings for invalid characters.
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
d5087d19c5 Extract parameter validation into a trait
Also, use parameterAllowlist() instead of the static property (so that we can e.g. override it later in SQLiteDatabaseManager, since overriding the static property doesn't work).
2026-04-29 17:35:11 +02:00
github-actions[bot]
182f3a2eb2 Fix code style (php-cs-fixer) 2026-04-29 12:16:22 +00: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
bdf592c0ff Add parameter validation to DB managers
DB manager methods validate the parameters they use in SQL statements using validateParameter() (excluding parameters passed via bindings in SELECT statements).
2026-04-29 14:13:56 +02:00
lukinovec
ad7d229daf Use parameter binding in SELECT queries 2026-04-29 10:21:47 +02:00
lukinovec
808f52765c Use select() instead of selectOne() in databaseExists() and userExists()
This is just for consistency, since all the other DB managers use select().
2026-04-29 10:08:45 +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
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
c32f52ce7c
phpstan fix: Scope generics
phpstan started failing with '... implements generic interface
Illuminate\Database\Eloquent\Scope but does not specify its types:
TModel'. We solve this by adding an implements docblock to the scopes
implementing that interface. They're fairly generic - we just use the
Model type itself in the code - so we use Model for the type parameter.
2026-04-15 11:23:12 +02:00
e31249dd09
Prevent mkdir() race conditions in FilesystemTenancyBootstrapper (#1453)
This prevents race conditions that may occur if there are two concurrent
processes trying to create the storage path for the tenant. The
storagePath() method runs during bootstrap() which can easily happen
in two places at once. The race condition specifically occurs in between
the is_dir() check and the mkdir() call, the latter producing an
exception if the dir already exist. We simply ignore any error coming
out of mkdir() and then check for success separately.

We could omit that success check since failure is unlikely and would
only occur due to a server misconfiguration that would manifest itself
in other ways as well, but this way the simple TOC/TOU race condition
is prevented while other errors are still reported.

We apply the same change to the mkdir() in scopeSessions() as the logic
is similar.

Resolves #1452
2026-04-13 23:57:59 +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
Samuel Mwangi
fb654e7a6b
[4.x] Update Pest to v4 (#1430) 2026-03-30 09:44:53 +02: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
8f3ea6297f
phpstan: change InputOption syntax 2026-03-09 02:13:37 +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
lukinovec
37b2a91aa9
[4.x] Fix URL override example in TenancyServiceProvider stub (#1426)
This PR fixes the URL override example in TenancyServiceProvider stub
(the commented `overrideUrlInTenantContext()` segment). If the tenant
doesn't have any domain, set the root URL back to the original one.
2026-01-14 11:18:15 +01:00
Punyapal Shah
e3701f1cc1
[4.x] Add more relation type annotations (#1424)
This pull request adds improved PHPDoc type annotations to several
Eloquent relationship methods, enhancing static analysis and developer
experience. These changes clarify the expected return types for
relationships, making the codebase easier to understand and work with.

Relationship method type annotations:

* Added a detailed return type annotation to the `tenant` method in the
`BelongsToTenant` trait, specifying the related model and the current
class.
* Added a detailed return type annotation to the `domains` method in the
`HasDomains` trait, specifying the related model and the current class.
* Added a detailed return type annotation to the `tenants` method in the
`ResourceSyncing` class, specifying the related model and the current
class.
2025-12-28 23:20:05 +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