CluPilotCloud/tests/Feature/OfficialDomainsTest.php

127 lines
5.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
use App\Support\OfficialDomains;
use App\Support\Settings;
/**
* Telling a customer which addresses are ours.
*
* The failure mode is specific and bad in both directions: a stale list tells
* somebody that a phishing domain is one of ours, or that one of ours is not.
* So the list is derived from configuration and nothing here is allowed to be
* a literal.
*/
beforeEach(function () {
config()->set('admin_access.site_hosts', ['www.clupilot.com', 'clupilot.com']);
config()->set('admin_access.app_host', 'app.clupilot.com');
config()->set('admin_access.status_host', 'status.clupilot.com');
Settings::set('provisioning.dns_zone', 'clupilot.cloud');
});
it('names both registrable domains, and says what each is for', function () {
// Keyed by domain and valued by role, so the page never works the role out
// from the order the list happens to come back in.
expect(OfficialDomains::registrable())->toBe([
'clupilot.com' => 'company',
'clupilot.cloud' => 'instances',
]);
});
it('follows the configuration rather than a list somebody typed', function () {
// The whole point. An installation moved to another zone must not go on
// vouching for the old one.
Settings::set('provisioning.dns_zone', 'kunden.example.test');
expect(array_keys(OfficialDomains::registrable()))->toContain('example.test')
->and(array_keys(OfficialDomains::registrable()))->not->toContain('clupilot.cloud');
});
it('shows the instance zone as a pattern, never one customers address', function () {
$hosts = collect(OfficialDomains::hosts())->pluck('host');
expect($hosts)->toContain('*.clupilot.cloud');
});
it('does not print where the operator console lives', function () {
// A public page listing the operator interface is a public page telling an
// attacker where to aim.
config()->set('admin_access.host', 'admin.clupilot.com');
$hosts = collect(OfficialDomains::hosts())->pluck('host');
expect($hosts)->not->toContain('admin.clupilot.com');
});
it('serves the page to somebody who is not signed in', function () {
// Somebody who has just given their password to a copy of our sign-in form
// is signed in nowhere. The page they need cannot be behind the thing they
// lost.
// The path is English (R13). /sicherheit shipped for two releases and
// still redirects, which is asserted below.
$this->get('/security')
->assertOk()
->assertSee('clupilot.com')
->assertSee('clupilot.cloud')
->assertSee(__('security.compromised_title'));
// The German path went out in mail footers before the rule was applied.
$this->get('/sicherheit')->assertRedirect('/security');
});
it('carries the domains in every mail footer', function () {
// The mail is the vector, so the line travels with the mail.
$layout = Illuminate\Support\Facades\File::get(resource_path('views/components/mail/layout.blade.php'));
expect($layout)->toContain('OfficialDomains::registrable()')
->toContain("route('security')");
});
it('says on the sign-in form which host it is', function () {
// This is the page a phishing kit copies, so the real one says how to tell.
$this->get(route('login'))
->assertOk()
->assertSee(__('auth.phishing_link'));
});
it('puts customer instances on their own registrable domain out of the box', function () {
// Asked for four times, and "change it in the console" was the wrong
// answer each time: an installation with nothing to migrate should simply
// arrive on the new zone. The migration does it, guarded by exactly the
// condition clupilot:check-zone reports on.
expect(App\Support\ProvisioningSettings::dnsZone())->toBe('clupilot.cloud');
});
it('keeps every public path in English', function () {
// R13, applied to ALL of them this time rather than to the one that was
// pointed at. A fragment is part of a URL too, and the section anchors were
// German for weeks after the rule was first cited.
$routes = collect(app('router')->getRoutes())
->map(fn ($route) => $route->uri())
->filter(fn (string $uri) => ! str_starts_with($uri, '_'));
$german = ['sicherheit', 'impressum', 'datenschutz', 'agb', 'preise', 'produkt', 'ablauf', 'fragen', 'kontakt', 'einstellungen', 'rechnungen'];
foreach ($routes as $uri) {
foreach ($german as $word) {
// The redirects that keep old addresses alive are the exception,
// and they are the only routes allowed to carry these words.
$isRedirect = collect(app('router')->getRoutes())
->first(fn ($r) => $r->uri() === $uri)?->getActionName() === 'Closure';
if (str_contains($uri, $word) && ! $isRedirect) {
expect($uri)->toBe("no German path: {$uri}");
}
}
}
// And the fragments the site links to.
foreach (['landing.blade.php', 'components/layouts/site.blade.php'] as $view) {
$source = Illuminate\Support\Facades\File::get(resource_path('views/'.$view));
foreach (['#produkt', '#ablauf', '#sicherheit', '#preise', '#fragen', '#kontakt'] as $fragment) {
expect($source)->not->toContain($fragment.'"')
->and($source)->not->toContain($fragment."'");
}
}
});