diff --git a/app/Http/Controllers/Gallery/PhotoController.php b/app/Http/Controllers/Gallery/PhotoController.php index 6916487b628..40fb71dfb5f 100644 --- a/app/Http/Controllers/Gallery/PhotoController.php +++ b/app/Http/Controllers/Gallery/PhotoController.php @@ -42,6 +42,7 @@ use App\Jobs\ExtractZip; use App\Jobs\ProcessImageJob; use App\Jobs\WatermarkerJob; +use App\Models\Extensions\BaseAlbum; use App\Models\Photo; use App\Models\SizeVariant; use App\Models\Tag; @@ -325,7 +326,28 @@ public function albums(GetPhotoAlbumsRequest $request): Collection $album_policy = resolve(AlbumPolicy::class); return $photo->albums - ->filter(fn ($album) => $album_policy->canAccess($user, $album)) + ->filter(function ($album) use ($album_policy, $user) { + if (!$album instanceof BaseAlbum) { + return $album_policy->canAccess($user, $album); + } + + // Owner always sees their albums in the list + if ($album_policy->isOwner($user, $album)) { + return true; + } + + // Explicitly shared albums are visible to the shared user + if ($album->current_user_permissions() !== null) { + return true; + } + + // Public albums are only shown if not link-required (hidden from listings) + $public_perm = $album->public_permissions(); + + return $public_perm !== null && + $public_perm->is_link_required === false && + ($public_perm->password === null || $album_policy->isUnlocked($album)); + }) ->values() ->map(fn ($album) => new PhotoAlbumResource($album)); } diff --git a/tests/Feature_v2/Photo/GetPhotoAlbumsTest.php b/tests/Feature_v2/Photo/GetPhotoAlbumsTest.php index 6b1babda064..b7163779f0b 100644 --- a/tests/Feature_v2/Photo/GetPhotoAlbumsTest.php +++ b/tests/Feature_v2/Photo/GetPhotoAlbumsTest.php @@ -18,6 +18,7 @@ namespace Tests\Feature_v2\Photo; +use App\Models\AccessPermission; use App\Models\Album; use App\Models\Photo; use Tests\Feature_v2\Base\BaseApiWithDataTest; @@ -104,4 +105,26 @@ public function testPhotoWithNoAlbumsReturnsEmptyArray(): void $response->assertJsonCount(0); $response->assertExactJson([]); } + + /** + * S-018-08: Hidden (link-required) albums are not shown to non-owners. + * + * Regression test for https://github.com/LycheeOrg/Lychee/discussions/4355 + */ + public function testHiddenAlbumNotShownToNonOwner(): void + { + // photo4 is in album4 (public+visible). Also place it in a new hidden album. + $hiddenAlbum = Album::factory()->as_root()->owned_by($this->userLocked)->create(); + // Public permission with is_link_required=true (hidden from listings) + AccessPermission::factory()->public()->for_album($hiddenAlbum)->create(); + $this->photo4->albums()->syncWithoutDetaching([$hiddenAlbum->id]); + + // A guest user should only see album4 (the visible one), NOT the hidden album + $response = $this->getJson('Photo/' . $this->photo4->id . '/albums'); + $this->assertOk($response); + + $response->assertJsonCount(1); + $response->assertJsonFragment(['id' => $this->album4->id]); + $response->assertJsonMissing(['id' => $hiddenAlbum->id]); + } }