fix(versions): non-dev installs only link the public repo on the Projekt card
The Versions "Projekt" card linked config('clusev.repository') — the per-install
update source, which on the dev box is the private Gitea. A staging/stable install
must never surface a private source. Now only the dev/release-control box shows its
actual source; every other install links the public open-core repo only, built from
the public slug with a canonical fallback (a malformed or full-URL slug can never
produce a private/garbage link). The update SOURCE (ReleaseChecker) is unchanged —
this governs only the displayed link.
Codex review: hardened the slug to reject non-owner/repo values; the release_controls
gate is the dev-box invariant (it also gates the dev-only /release page, and a staging
box's CLUSEV_REPOSITORY is the public repo anyway).
Verified: 432 tests green (incl. dev / non-dev / malformed-slug cases), pint clean,
/versions loads 200 with zero console errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/v1-foundation
parent
05c34b81ed
commit
8e77f31f00
|
|
@ -563,6 +563,32 @@ class Index extends Component
|
|||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* The repository shown on the "Projekt" card. Only the dev/release-control box surfaces its
|
||||
* actual update source (which may be the private Gitea); EVERY other install — staging or
|
||||
* stable — links the public open-core repo only, so a non-dev server can never expose a private
|
||||
* source regardless of how its CLUSEV_REPOSITORY happens to be set. (The update SOURCE is still
|
||||
* config('clusev.repository'); this only governs the displayed project link.)
|
||||
*
|
||||
* release_controls=true is the dev-box invariant: it also gates the dev-only /release page + the
|
||||
* host release bridge, so a staging/stable box never has it on — and even if it did, that box's
|
||||
* CLUSEV_REPOSITORY is the public repo anyway (it pulls from public). On a non-dev box the link
|
||||
* is built from the public slug ONLY, and a malformed slug (empty / slashed / a full URL) falls
|
||||
* back to the canonical public repo — never to a private host.
|
||||
*/
|
||||
private function projectUrl(): string
|
||||
{
|
||||
if (config('clusev.release_controls')) {
|
||||
return (string) config('clusev.repository');
|
||||
}
|
||||
$slug = trim((string) config('clusev.public_slug'), '/');
|
||||
if (! preg_match('#^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$#', $slug)) {
|
||||
$slug = 'clusev/clusev';
|
||||
}
|
||||
|
||||
return 'https://github.com/'.$slug;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$releases = $this->releases();
|
||||
|
|
@ -602,7 +628,7 @@ class Index extends Component
|
|||
return view('livewire.versions.index', [
|
||||
'version' => config('clusev.version'),
|
||||
'channel' => $this->channel(),
|
||||
'repository' => config('clusev.repository'),
|
||||
'repository' => $this->projectUrl(),
|
||||
'license' => config('clusev.license'),
|
||||
'build' => $this->build(),
|
||||
'seriesList' => $seriesList,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use Illuminate\Support\Facades\Cache;
|
|||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Livewire\Livewire;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
|
|
@ -304,4 +305,60 @@ class VersionUpdateCheckTest extends TestCase
|
|||
'the per-user throttle must block the sentinel write',
|
||||
);
|
||||
}
|
||||
|
||||
// ── Project link: only the dev box may expose its (possibly private) source ───
|
||||
|
||||
public function test_dev_box_shows_its_actual_repository_on_the_project_card(): void
|
||||
{
|
||||
config()->set('clusev.release_controls', true); // the dev / release-control box
|
||||
Http::fake();
|
||||
|
||||
// setUp() set clusev.repository to the (Gitea-style) source — the dev box may surface it.
|
||||
Livewire::test(Index::class)->assertViewHas('repository', self::REPO);
|
||||
}
|
||||
|
||||
public function test_non_dev_install_only_links_the_public_repo(): void
|
||||
{
|
||||
config()->set('clusev.release_controls', false); // staging / stable
|
||||
config()->set('clusev.public_slug', 'clusev/clusev');
|
||||
Http::fake();
|
||||
|
||||
// Even though clusev.repository points at the private Gitea source (setUp), the project card
|
||||
// must link ONLY the public repo — a non-dev server never exposes the private source.
|
||||
Livewire::test(Index::class)->assertViewHas('repository', 'https://github.com/clusev/clusev');
|
||||
}
|
||||
|
||||
public function test_non_dev_project_link_follows_the_public_slug(): void
|
||||
{
|
||||
config()->set('clusev.release_controls', false);
|
||||
config()->set('clusev.public_slug', 'acme/pub');
|
||||
Http::fake();
|
||||
|
||||
Livewire::test(Index::class)->assertViewHas('repository', 'https://github.com/acme/pub');
|
||||
}
|
||||
|
||||
/**
|
||||
* A malformed public_slug must never produce a private/garbage link — it falls back to the
|
||||
* canonical public repo. Covers empty, surrounding slashes, and a slug mistakenly set to a full
|
||||
* (possibly private) URL.
|
||||
*/
|
||||
#[DataProvider('malformedSlugs')]
|
||||
public function test_non_dev_project_link_falls_back_for_a_malformed_slug(string $slug, string $expected): void
|
||||
{
|
||||
config()->set('clusev.release_controls', false);
|
||||
config()->set('clusev.public_slug', $slug);
|
||||
Http::fake();
|
||||
|
||||
Livewire::test(Index::class)->assertViewHas('repository', $expected);
|
||||
}
|
||||
|
||||
/** @return array<string,array{0:string,1:string}> */
|
||||
public static function malformedSlugs(): array
|
||||
{
|
||||
return [
|
||||
'empty' => ['', 'https://github.com/clusev/clusev'],
|
||||
'surrounding slashes' => ['/acme/pub/', 'https://github.com/acme/pub'],
|
||||
'full private url' => ['https://gitea.internal/o/repo', 'https://github.com/clusev/clusev'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue