1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-12-05 17:22:06 +03:00

Merge branch 'development' into release

This commit is contained in:
Dan Brown
2025-11-21 14:01:06 +00:00
5 changed files with 102 additions and 30 deletions

View File

@@ -26,6 +26,13 @@ DB_DATABASE=database_database
DB_USERNAME=database_username
DB_PASSWORD=database_user_password
# Storage system to use
# By default files are stored on the local filesystem, with images being placed in
# public web space so they can be efficiently served directly by the web-server.
# For other options with different security levels & considerations, refer to:
# https://www.bookstackapp.com/docs/admin/upload-config/
STORAGE_TYPE=local
# Mail system to use
# Can be 'smtp' or 'sendmail'
MAIL_DRIVER=smtp

View File

@@ -264,7 +264,7 @@ class ImageService
return false;
}
if ($this->storage->usingSecureImages() && user()->isGuest()) {
if ($this->blockedBySecureImages()) {
return false;
}
@@ -280,13 +280,24 @@ class ImageService
return false;
}
if ($this->storage->usingSecureImages() && user()->isGuest()) {
if ($this->blockedBySecureImages()) {
return false;
}
return $this->imageFileExists($image->path, $image->type);
}
/**
* Check if the current user should be blocked from accessing images based on if secure images are enabled
* and if public access is enabled for the application.
*/
protected function blockedBySecureImages(): bool
{
$enforced = $this->storage->usingSecureImages() && !setting('app-public');
return $enforced && user()->isGuest();
}
/**
* Check if the given image path exists for the given image type and that it is likely an image file.
*/

View File

@@ -74,7 +74,7 @@ class ImageStorage
return 'local';
}
// Rename local_secure options to get our image specific storage driver which
// Rename local_secure options to get our image-specific storage driver, which
// is scoped to the relevant image directories.
if ($localSecureInUse) {
return 'local_secure_images';

54
composer.lock generated
View File

@@ -62,16 +62,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.360.0",
"version": "3.362.1",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "a21055795be59f3d7c5ca6e4d52a80930dcf8c20"
"reference": "f29a49b74d5ee771f13432e16d58651de91f7e79"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a21055795be59f3d7c5ca6e4d52a80930dcf8c20",
"reference": "a21055795be59f3d7c5ca6e4d52a80930dcf8c20",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f29a49b74d5ee771f13432e16d58651de91f7e79",
"reference": "f29a49b74d5ee771f13432e16d58651de91f7e79",
"shasum": ""
},
"require": {
@@ -153,22 +153,22 @@
"support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.360.0"
"source": "https://github.com/aws/aws-sdk-php/tree/3.362.1"
},
"time": "2025-11-17T19:46:19+00:00"
"time": "2025-11-20T19:10:40+00:00"
},
{
"name": "bacon/bacon-qr-code",
"version": "v3.0.2",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/Bacon/BaconQrCode.git",
"reference": "fe259c55425b8178f77fb6d1f84ba2473e21ed55"
"reference": "36a1cb2b81493fa5b82e50bf8068bf84d1542563"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/fe259c55425b8178f77fb6d1f84ba2473e21ed55",
"reference": "fe259c55425b8178f77fb6d1f84ba2473e21ed55",
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/36a1cb2b81493fa5b82e50bf8068bf84d1542563",
"reference": "36a1cb2b81493fa5b82e50bf8068bf84d1542563",
"shasum": ""
},
"require": {
@@ -208,9 +208,9 @@
"homepage": "https://github.com/Bacon/BaconQrCode",
"support": {
"issues": "https://github.com/Bacon/BaconQrCode/issues",
"source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.2"
"source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.3"
},
"time": "2025-11-16T22:59:48+00:00"
"time": "2025-11-19T17:15:36+00:00"
},
{
"name": "brick/math",
@@ -3613,31 +3613,31 @@
},
{
"name": "nunomaduro/termwind",
"version": "v2.3.2",
"version": "v2.3.3",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/termwind.git",
"reference": "eb61920a53057a7debd718a5b89c2178032b52c0"
"reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/termwind/zipball/eb61920a53057a7debd718a5b89c2178032b52c0",
"reference": "eb61920a53057a7debd718a5b89c2178032b52c0",
"url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017",
"reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^8.2",
"symfony/console": "^7.3.4"
"symfony/console": "^7.3.6"
},
"require-dev": {
"illuminate/console": "^11.46.1",
"laravel/pint": "^1.25.1",
"mockery/mockery": "^1.6.12",
"pestphp/pest": "^2.36.0 || ^3.8.4",
"pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3",
"phpstan/phpstan": "^1.12.32",
"phpstan/phpstan-strict-rules": "^1.6.2",
"symfony/var-dumper": "^7.3.4",
"symfony/var-dumper": "^7.3.5",
"thecodingmachine/phpstan-strict-rules": "^1.0.0"
},
"type": "library",
@@ -3680,7 +3680,7 @@
],
"support": {
"issues": "https://github.com/nunomaduro/termwind/issues",
"source": "https://github.com/nunomaduro/termwind/tree/v2.3.2"
"source": "https://github.com/nunomaduro/termwind/tree/v2.3.3"
},
"funding": [
{
@@ -3696,7 +3696,7 @@
"type": "github"
}
],
"time": "2025-10-18T11:10:27+00:00"
"time": "2025-11-20T02:34:59+00:00"
},
{
"name": "onelogin/php-saml",
@@ -8587,16 +8587,16 @@
},
{
"name": "nunomaduro/collision",
"version": "v8.8.2",
"version": "v8.8.3",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
"reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb"
"reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/60207965f9b7b7a4ce15a0f75d57f9dadb105bdb",
"reference": "60207965f9b7b7a4ce15a0f75d57f9dadb105bdb",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/1dc9e88d105699d0fee8bb18890f41b274f6b4c4",
"reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4",
"shasum": ""
},
"require": {
@@ -8618,7 +8618,7 @@
"laravel/sanctum": "^4.1.1",
"laravel/tinker": "^2.10.1",
"orchestra/testbench-core": "^9.12.0 || ^10.4",
"pestphp/pest": "^3.8.2",
"pestphp/pest": "^3.8.2 || ^4.0.0",
"sebastian/environment": "^7.2.1 || ^8.0"
},
"type": "library",
@@ -8682,7 +8682,7 @@
"type": "patreon"
}
],
"time": "2025-06-25T02:12:12+00:00"
"time": "2025-11-20T02:55:25+00:00"
},
{
"name": "phar-io/manifest",

View File

@@ -5,6 +5,8 @@ namespace Tests\Uploads;
use BookStack\Entities\Repos\PageRepo;
use BookStack\Uploads\Image;
use BookStack\Uploads\ImageService;
use BookStack\Uploads\UserAvatars;
use BookStack\Users\Models\Role;
use Illuminate\Support\Str;
use Tests\TestCase;
@@ -467,6 +469,26 @@ class ImageTest extends TestCase
}
}
public function test_avatar_images_visible_only_when_public_access_enabled_with_local_secure_restricted()
{
config()->set('filesystems.images', 'local_secure_restricted');
$user = $this->users->admin();
$avatars = $this->app->make(UserAvatars::class);
$avatars->assignToUserFromExistingData($user, $this->files->pngImageData(), 'png');
$avatarUrl = $user->getAvatar();
$resp = $this->get($avatarUrl);
$resp->assertRedirect('/login');
$this->permissions->makeAppPublic();
$resp = $this->get($avatarUrl);
$resp->assertOk();
$this->files->deleteAtRelativePath($user->avatar->path);
}
public function test_secure_restricted_images_inaccessible_without_relation_permission()
{
config()->set('filesystems.images', 'local_secure_restricted');
@@ -491,6 +513,38 @@ class ImageTest extends TestCase
}
}
public function test_secure_restricted_images_accessible_with_public_guest_access()
{
config()->set('filesystems.images', 'local_secure_restricted');
$this->permissions->makeAppPublic();
$this->asEditor();
$page = $this->entities->page();
$this->files->uploadGalleryImageToPage($this, $page);
$image = Image::query()->where('type', '=', 'gallery')
->where('uploaded_to', '=', $page->id)
->first();
$expectedUrl = url($image->path);
$expectedPath = storage_path($image->path);
auth()->logout();
$this->get($expectedUrl)->assertOk();
$this->permissions->setEntityPermissions($page, [], []);
$resp = $this->get($expectedUrl);
$resp->assertNotFound();
$this->permissions->setEntityPermissions($page, ['view'], [Role::getSystemRole('public')]);
$this->get($expectedUrl)->assertOk();
if (file_exists($expectedPath)) {
unlink($expectedPath);
}
}
public function test_thumbnail_path_handled_by_secure_restricted_images()
{
config()->set('filesystems.images', 'local_secure_restricted');