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

Throw an exception in TenantAssetController if the disk is not tenant-aware

Instead of just saying that the publicDisk *should* be listed in tenancy.filesystem.disks, enforce that -- if the disk isn't tenant-aware, throw an exception.

Also update comments accordingly. E.g. since scoped disks don't have to have a single "parent disk" (the parent can also be a scoped disk and have another parent, and so on), use "base disk".
This commit is contained in:
lukinovec 2026-09-02 14:42:23 +02:00 committed by Samuel Stancl
parent 041b023a95
commit e0e696f83e

View file

@ -47,10 +47,10 @@ class TenantAssetController implements HasMiddleware
* When null, the assets are served from app/public inside the tenant's storage directory. * When null, the assets are served from app/public inside the tenant's storage directory.
* *
* The disk has to be local, since the assets are read from the filesystem. Disks using the * The disk has to be local, since the assets are read from the filesystem. Disks using the
* 'scoped' driver are supported as long as their parent disk uses the 'local' driver. * 'scoped' driver are supported as long as the disk they're based on uses the 'local' driver.
* *
* It should also be listed in tenancy.filesystem.disks -- for scoped disks, it's the parent * The disk also has to be listed in tenancy.filesystem.disks -- for scoped disks, it's the
* disk that has to be listed there (since a scoped disk inherits the parent's root). * disk they're based on that has to be listed there (since a scoped disk inherits its root).
* FilesystemTenancyBootstrapper only scopes the roots of disks listed there, so * FilesystemTenancyBootstrapper only scopes the roots of disks listed there, so
* without that every tenant would be served the same (central) directory. * without that every tenant would be served the same (central) directory.
*/ */
@ -87,8 +87,8 @@ class TenantAssetController implements HasMiddleware
/** /**
* Directory the assets are served from -- the root of the $publicDisk, or app/public * Directory the assets are served from -- the root of the $publicDisk, or app/public
* inside the tenant's storage directory when no disk is configured. With no current * inside the tenant's storage directory when no disk is configured. With no disk and
* tenant (e.g. on a universal route), the central storage directory is used. * no current tenant (e.g. on a universal route), the central app/public is used.
* *
* The tenant's storage directory is resolved using the FilesystemTenancyBootstrapper (rather * The tenant's storage directory is resolved using the FilesystemTenancyBootstrapper (rather
* than storage_path(), so that it's tenant-scoped regardless of the suffix_storage_path config). * than storage_path(), so that it's tenant-scoped regardless of the suffix_storage_path config).
@ -99,13 +99,20 @@ class TenantAssetController implements HasMiddleware
$disk = Storage::disk(static::$publicDisk); $disk = Storage::disk(static::$publicDisk);
if (! $disk instanceof LocalFilesystemAdapter) { if (! $disk instanceof LocalFilesystemAdapter) {
// The root has to be read from the resolved disk rather than from the disk's config,
// since the config root isn't the full root path of every local disk. Disks using the
// 'scoped' driver have no root in their config -- they inherit their parent disk's root --
// and a 'prefix' is part of the root path as well.
throw new Exception('Disk [' . static::$publicDisk . '] is not a local disk. Only local disks can be used for serving assets.'); throw new Exception('Disk [' . static::$publicDisk . '] is not a local disk. Only local disks can be used for serving assets.');
} }
$baseDiskName = $this->baseDiskName(static::$publicDisk);
if (! in_array($baseDiskName, config('tenancy.filesystem.disks'), true)) {
// FilesystemTenancyBootstrapper only scopes the roots of disks listed in tenancy.filesystem.disks.
// Without that, the root stays central and every tenant would be served the same directory.
throw new Exception("Disk [$baseDiskName] is not tenant-aware. Add it to the tenancy.filesystem.disks config to make its root tenant-specific.");
}
// The root is read from the resolved disk rather than from the disk's config, since the
// config root isn't the full root path of every local disk. Disks using the 'scoped' driver
// have no root in their config, and a 'prefix' is part of the root path as well.
return rtrim($disk->path(''), DIRECTORY_SEPARATOR); return rtrim($disk->path(''), DIRECTORY_SEPARATOR);
} }
@ -116,6 +123,26 @@ class TenantAssetController implements HasMiddleware
return storage_path('app/public'); return storage_path('app/public');
} }
/**
* Name of the disk whose root the passed disk uses.
*
* Disks using the 'scoped' driver have no root of their own -- they inherit the root of their parent disk,
* which can be scoped as well, so the final/base parent is what has to be tenant-aware.
*/
protected function baseDiskName(string $disk): string
{
while (config("filesystems.disks.$disk.driver") === 'scoped') {
if (! is_string($parent = config("filesystems.disks.$disk.disk"))) {
// Laravel allows configuring the parent inline as an array, in which case it has no name
throw new Exception("Disk [$disk] has its parent disk configured inline. Use a named parent disk listed in tenancy.filesystem.disks.");
}
$disk = $parent;
}
return $disk;
}
/** /**
* Prevent path traversal attacks. This is generally a non-issue on modern * Prevent path traversal attacks. This is generally a non-issue on modern
* webservers but it's still worth handling on the application level as well. * webservers but it's still worth handling on the application level as well.