1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-08-06 22:14:03 +00:00

Merge branch 'master' into broadcasting-fixes

This commit is contained in:
lukinovec 2026-06-29 13:19:26 +02:00 committed by GitHub
commit f20f8016d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 1586 additions and 210 deletions

View file

@ -16,6 +16,15 @@ use Stancl\Tenancy\Contracts\Tenant;
/**
* Makes cache tenant-aware by applying a prefix.
*
* Using this bootstrapper together with DatabaseTenancyBootstrapper
* with a database cache store will result in "double scoping". The store will be scoped
* by the DB connection (entries will go into the tenant's database) *and* by the prefix.
* This is harmless in most cases, but is important to be aware of.
*
* If you only use database cache stores, consider using DatabaseCacheBootstrapper instead.
*
* @see Stancl\Tenancy\Bootstrappers\DatabaseCacheBootstrapper
*/
class CacheTenancyBootstrapper implements TenancyBootstrapper
{

View file

@ -21,11 +21,15 @@ use Stancl\Tenancy\TenancyServiceProvider;
*
* By default, this bootstrapper scopes ALL cache stores that use the database driver. If you only
* want to scope SOME stores, set the static $stores property to an array of names of the stores
* you want to scope. These stores must use 'database' as their driver.
* you want to scope. Those stores must use 'database' as their driver.
*
* Notably, this bootstrapper sets TenancyServiceProvider::$adjustCacheManagerUsing to a callback
* that ensures all affected stores still use the central connection when accessed via global cache
* (typicaly the GlobalCache facade or global_cache() helper).
* (typically the GlobalCache facade or global_cache() helper). The code in TenancyServiceProvider
* that uses `extend()` callbacks to make database stores on the global cache manager use the central
* connection only corrects stores scoped by the Database*Tenancy*Bootstrapper. This bootstrapper
* also changes the stores' connection in the *config* to 'tenant' which doesn't let that callback
* change the connection back to central on the global cache manager.
*/
class DatabaseCacheBootstrapper implements TenancyBootstrapper
{

View file

@ -5,14 +5,42 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Bootstrappers;
use Exception;
use Illuminate\Support\Facades\Schema;
use RuntimeException;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\DatabaseManager;
use Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException;
use Throwable;
class DatabaseTenancyBootstrapper implements TenancyBootstrapper
{
/**
* When true, throw an exception if a tenant gets connected to
* another tenant's database or to the central database.
*
* This case should never come up in well-configured apps where
* users cannot set or edit tenant IDs or database names, so this
* option is disabled by default.
*
* However, applications dealing with extremely sensitive data may
* choose to enable this runtime check to prevent a bug or misconfiguration
* from creating an exploit that would let an attacker access another
* tenant's data or data from the central database.
*
* One way such a scenario might come up is if an application allows
* broad tenant attribute updates on a page for updating some fields
* on the tenant, without restricting that action to only a limited
* set of fields that are safe to edit. An attacker might be able to add
* something like ['tenancy_db_name' => '...'] to the request which could
* lead to this internal attribute being updated on an existing tenant.
*
* It's possible that enabling this setting will negate the performance
* benefits of cached tenant lookup.
*/
public static bool $harden = false;
/** @var DatabaseManager */
protected $database;
@ -41,10 +69,39 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper
}
$this->database->connectToTenant($tenant);
if (static::$harden) {
try {
$this->verifyTenantCanUseDatabase($tenant);
} catch (Throwable $e) {
// Revert connection back to central
$this->revert();
throw $e;
}
}
}
public function revert(): void
{
$this->database->reconnectToCentral();
}
protected function verifyTenantCanUseDatabase(Tenant $tenant): void
{
/** @var \Stancl\Tenancy\Database\Models\Tenant&TenantWithDatabase $tenant */
$tenantDbName = $tenant->database()->getName();
// Check that no other tenant uses this tenant's database
if ($tenant::where($tenant->getTenantKeyName(), '!=', $tenant->getTenantKey())
->where($tenant::getDataColumn() . '->' . $tenant->internalPrefix() . 'db_name', $tenantDbName)
->exists()) {
throw new RuntimeException('Tenant cannot use a database of another tenant.');
}
if (Schema::hasTable($tenant->getTable())) {
// Throw if the current database/schema has the tenants table (i.e. it's not central)
throw new RuntimeException('Tenant cannot use the central database.');
}
}
}

View file

@ -16,7 +16,7 @@ use Stancl\Tenancy\Resolvers\PathTenantResolver;
/**
* Makes the app use TenancyUrlGenerator (instead of Illuminate\Routing\UrlGenerator) which:
* - prefixes route names with the tenant route name prefix (PathTenantResolver::tenantRouteNamePrefix() by default)
* - passes the tenant parameter to the link generated by route() and temporarySignedRoute() (PathTenantResolver::tenantParameterName() by default).
* - passes the tenant parameter (PathTenantResolver::tenantParameterName() by default) to the link generated by the affected methods like route() and temporarySignedRoute().
*
* Used with path and query string identification.
*