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.
Also make all resource syncing-related listener closures static.
Also correct return type for getGlobalIdentifierKey to string|int.
(We intentionally do not support returning null like many other
"get x key" methods would since such a case might break resource
syncing logic. This is also why we use inline getAttribute() in the
creating listener instead of calling the method.)
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.
This change was prompted by a phpstan failure after a recent update.
While making this change, I noticed we don't need the macro anymore
as useAssetOrigin() was added to the UrlGenerator earlier this year,
simplifying our implementation.
Recent Laravel installations often have http://localhost:8000 as
APP_URL, so we make sure to strip any port suffix from the default
central domain derived from APP_URL.
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".
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.
Previously, if a universal route was cloned without a
cloneRoutesWithMiddleware(['universal']) call, i.e. it had both
'clone' and 'universal' flags, with only the former triggering cloning,
the 'universal' flag would be included in the middleware of the cloned
route.
Now, we make sure to remove all context flags -- central, tenant,
universal -- in the first step of processing middleware, before adding
just 'tenant'.
`?` parameters are not supported in these statements, so we have to use
string interpolation like in other related code.
---------
Co-authored-by: Samuel Štancl <samuel@archte.ch>
With the previous implementation, many users would use the default
config that enables scope_sessions. They would then deploy the app
to production and get the exception there since they use the
`database` session driver which is scoped by a different mechanism.
The idea behind throwing the exception only in prod was to make it
easy to use different setups locally without getting annoying
exceptions, while notifying users that a security feature they enabled
isn't running in production.
However, a better way of doing this is to just throw the exception
consistently in all setups and use a sane default for enabling the
scope_sessions setting based on the SESSION_DRIVER env var.
Users are always encouraged to read the session scoping docs to make
sure their session scoping configuration makes sense for their specific
setup, but this is a good balance for providing solid security out of
the box for most setups without requiring users to configure things
manually.
This is because the CreateTenantStorage listener only runs when
a tenant is created, but in multi-server setups the directory may
need to be created each time a tenant is *used*, not just created.
Also changed the listeners to use TenantEvent instead of specific
events, to make it possible to use them with other events, such as
TenancyBootstrapped.
Also update permission bits in a few mkdir() calls to better scope
data to the current OS user.
Also fix a typo in CacheTenancyBootstrapper (exception message).
By purging stores, we "detach" existing cache stores from the
CacheManager, making them impossible to adjust in the future.
We also unnecessarily recreate them on every tenancy bootstrap/revert.
A simpler case where this causes problems is defining a RateLimiter in
a service provider. That injects a single cache store into the
rate limiter singleton, which then becomes a completely independent
object after tenancy is initialized due to the purge. This in turn
means the central and tenant contexts share the rate limiter cache
instead of using separate caches as one would expect.
This PR makes the expired/invalid tenant impersonation tokens get
deleted instead of just aborting with 403.
The PR also adds a command (ClearExpiredImpersonationTokens) used like
`php artisan tenants:purge-impersonation-tokens`. As the name suggests,
it clears all expired impersonation tokens (= tokens older than
`UserImpersonation::$ttl`).
Resolves#1348
---------
Co-authored-by: Samuel Štancl <samuel@archte.ch>
Before, when using UrlGeneratorBootstrapper, and your app had a
`https://` url, in tenant context, the url would have the `http://`
scheme.
Now, the bootstrapper makes sure that the TenancyUrlGenerator inherits
the original UrlGenerator's scheme. So if your app has e.g. url
"https://some-url.test", `route('home')` in tenant context will return
"http**s**://some-url.test/home" (originally, you'd get
"http://some-url.test/home" - the original scheme - https - wouldn't be
respected in the tenant context).
This PR addresses the issue reported on Discord
(https://discord.com/channels/976506366502006874/976506736120823909/1399012794514411621).
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Samuel Štancl <samuel@archte.ch>
This method lets the user specify default values for custom
non-nullable columns. The primary use case is when the tenants table
has a column like 'slug' and createPending() is called with no
value for 'slug'. This would produce an exception due to the column
having no default value.
Here, getPendingAttributes() can set an initial dummy slug (like a
randomly generated string) before it's overwritten during a pull.
getPendingAttributes() accepts an $attributes array which corresponds
to the attributes passed to createPending(). The array returned from
getPendingAttributes() is ultimately merged with $attributes, so
the user doesn't need to use the $attributes value in
getPendingAttributes(), however it serves to provide more context when
the pending attributes might be dependent on $attributes and therefore
derived from the $attributes actually being used.
Also fixed the `finally` branch in createPending() as it was
potentially referencing the $tenant variable before it was initialized.
The change in SQLiteDatabaseManager wasn't properly saving the
updated internal value.
The check in CacheTenancyBootstrapper wasn't handling that local tests
have a 'testing' environment, not local. However fixing only the
condition would've still added the store to $names which would throw
an exception down the line. We make sure to only throw the exception
in prod, but also make sure to only add the store to $names if it is
supported.
This commit adds support for building a docker image based on PHP 8.5
(RC). It also removes some unused code in tests that was triggering
deprecation warnings. For similar deprecation warnings coming from
testbench we have a temporary patch script until this is resolved
upstream.
This commit also adds logic to the DisallowSqliteAttach feature
leveraging the new native setAuthorizer() method, instead of loading
a compiled extension.
We also remove the unused `php` parameter from ci.yml
assert() calls, including assert(foo()), can be entirely compiled out
depending on the INI settings described here:
https://www.php.net/manual/en/function.assert.php
That in turn means even side effects of foo() can be entirely compiled
out.
Therefore, to ensure the call actually runs, we need to run it before
the assert(), store its return value, and only then make assertions
about the return value.
Pretty much all errors that can happen in createDatabase() end up
throwing an exception, however the function still does return a boolean
(it bubbles up the value from the underlying $conn->statement() call)
which should be checked in at least some way.
From the perspective of the master branch, this commit merges in a
few small breaking changes from the dev branch:
6b0066c5ef
- Make pullPendingFromPool() $firstOrCreate arg default to false
(pullPending() is now a direct alias for pullPendingFromPool() with
default $firstOrCreate=true)
- See full commit message for other changes. They shouldn't be breaking
though.
13a2209f11
- Remove $WAL static property. We instead just let Laravel use its
journal_mode config now
This merge also adds a deprecation:
b320f8f33d
- Deprecate TenantConfig feature in favor of TenantConfigBootstrapper
This commit also corrects an Event::fake() call in a separate test, as
general Event::fake() calls without specified events can lead to
incorrect (and difficult to debug) behavior in some cases, since
Tenancy depends on the event system being functional.
Remove comments about shouldBeQueued(true) being preferable in
production as that isn't necessarily true anymore with pending tenants
(or even the absence of any "optimizations", they're all optional).
Using queued tenant creation also requires some code changes in the
tenant onboarding logic, so it is misleading to imply that it's a
switch that should simply be turned on in production.
Add DatabaseCacheBootstrapper to config.php as it was missing there.
Remove note about MailConfigBootstrapper needing forceRefresh in the
QueueTenancyBootstrapper as we now use a non-persistent queue
bootstrapper by default.
Notable changes:
- CreateUserWithRLSPolicies: Clarify why we're creating a custom
DatabaseConfing instance
- HasDatabase: Clarify why we're ignoring tenancy_db_connection
- DatabaseConfig: General refactor, clarify the role of the host conn
- SQLiteDatabaseManager: Handle trailing DIRECTORY_SEPARATOR
in static::$path
- DisallowSqliteAttach: Don't throw any exceptions, just silently fail
since the class isn't 100% portable
- Clean up todos that are no longer relevant
- Clean up dead code or comments in some database managers
The feature was pretty much a soft-bootstrapper -- it listened
to both Bootstrapped and Reverted. Bootstrappers have a few more
protections in terms of error handling and safe reverting, so there's
no point in (badly) re-implementing bootstrapper functionality within
TenantConfig just so it could be a Feature.
Going forward, all Features should be things that are mostly agnostic
of the tenant state, and especially they should not use bootstrapped/
reverted events. Bootstrappers are simply more appropriate and safe.
- (BC BREAK) Remove $WAL static property. We instead just let
Laravel use its journal_mode config now
- Remove journal, wal, and shm files when deleting tenant DB
- Check that the system is 64-bit when using NoAttach (we don't
build 32 bit extensions)
- Use local static instead of a class static property for caching
loadExtensionSupported
Features are now *always* bootstrapped, even if Tenancy is not resolved
from the container.
Previous implementations include
https://github.com/tenancy-for-laravel/v4/pull/19https://github.com/archtechx/tenancy/pull/1021
Bug originally reported here
https://github.com/archtechx/tenancy/issues/949
This implementation is much simpler, we do not distinguish between
features that should be "always bootstrapped" and features that should
only be bootstrapped after Tenancy is resolved. All features should work
without issues if they're bootstrapped when TSP::boot() is called. We
also add a Tenancy::bootstrapFeatures() method that can be used to
bootstrap any features dynamically added at runtime that weren't
bootstrapped in TSP::boot(). The function keeps track of which features
were already bootstrapped so it doesn't bootstrap them again.
The only potentialy risky thing in this implementation is that we're now
resolving Tenancy in TSP::boot() (previously Tenancy was not being
resolved) but that shouldn't be causing any issues.