fix: load markdown in direct.php, allow video-channels, guard modals
This commit is contained in:
@@ -88,3 +88,28 @@ def test_video_page_invalid_id_redirects(page, base_url):
|
||||
page.goto(base_url + "/video.php?id=pas-un-uuid-valide",
|
||||
wait_until="domcontentloaded")
|
||||
assert page.url.endswith("/index.php"), f"URL après redirection : {page.url}"
|
||||
|
||||
|
||||
def test_video_page_not_found_no_js_errors(page, base_url):
|
||||
"""Un UUID bien formé mais inexistant affiche « Vidéo non trouvée » sans erreur JS."""
|
||||
erreurs_js = []
|
||||
page.on("pageerror", lambda exc: erreurs_js.append(str(exc)))
|
||||
page.goto(base_url + "/video.php?id=00000000-0000-0000-0000-000000000000",
|
||||
wait_until="load")
|
||||
|
||||
# La page d'erreur est affichée (pas de redirection : l'UUID est bien formé ;
|
||||
# si l'API est indisponible, la réponse vide mène au même état)
|
||||
assert page.locator(".error-message").count() == 1, "Le message d'erreur est absent"
|
||||
assert page.locator("#error-heading").inner_text() == "Vidéo non trouvée"
|
||||
|
||||
# Les modales téléchargement/partage et leur script ne doivent pas être rendus
|
||||
assert page.locator("#download-modal").count() == 0, (
|
||||
"La modale de téléchargement est présente sur la page d'erreur"
|
||||
)
|
||||
assert page.locator("#share-modal").count() == 0, (
|
||||
"La modale de partage est présente sur la page d'erreur"
|
||||
)
|
||||
|
||||
assert erreurs_js == [], (
|
||||
f"Erreurs JS sur la page « vidéo introuvable » : {erreurs_js}"
|
||||
)
|
||||
|
||||
@@ -141,6 +141,18 @@ assertTrue(
|
||||
isValidApiEndpoint('accounts/annu_kute_ced/videos'),
|
||||
'isValidApiEndpoint accepte les vidéos d\'un compte'
|
||||
);
|
||||
assertTrue(
|
||||
isValidApiEndpoint('video-channels/annu_kute_ced/videos'),
|
||||
'isValidApiEndpoint accepte les vidéos d\'une chaîne'
|
||||
);
|
||||
assertFalse(
|
||||
isValidApiEndpoint('video-channels/annu_kute_ced'),
|
||||
'isValidApiEndpoint refuse une chaîne sans sous-chemin /videos'
|
||||
);
|
||||
assertFalse(
|
||||
isValidApiEndpoint('video-channels'),
|
||||
'isValidApiEndpoint refuse "video-channels" seul'
|
||||
);
|
||||
assertFalse(isValidApiEndpoint('../config'), 'isValidApiEndpoint refuse le path traversal');
|
||||
assertFalse(isValidApiEndpoint('videos/../x'), 'isValidApiEndpoint refuse ".." dans le chemin');
|
||||
assertFalse(isValidApiEndpoint('videos//categories'), 'isValidApiEndpoint refuse un double slash');
|
||||
@@ -155,3 +167,91 @@ $nonce = getCspNonce();
|
||||
assertTrue(is_string($nonce) && $nonce !== '', 'getCspNonce retourne une chaîne non vide');
|
||||
assertEquals(16, strlen(base64_decode($nonce, true)), 'getCspNonce est un base64 de 16 octets aléatoires');
|
||||
assertEquals($nonce, getCspNonce(), 'getCspNonce retourne le même nonce durant toute la requête');
|
||||
|
||||
// --- getCsrfSecret ------------------------------------------------------------
|
||||
|
||||
assertEquals(
|
||||
CSRF_SECRET,
|
||||
getCsrfSecret(),
|
||||
'getCsrfSecret retourne CSRF_SECRET quand il est configuré'
|
||||
);
|
||||
|
||||
// Repli éphémère : impossible à tester dans ce processus (CSRF_SECRET est déjà
|
||||
// défini par le bootstrap), on passe par un sous-processus PHP isolé dont la
|
||||
// sortie d'erreur est redirigée vers stdout pour capter l'avertissement.
|
||||
$securityFile = dirname(__DIR__, 2) . '/includes/security.php';
|
||||
$snippet = <<<'PHP'
|
||||
require $argv[1];
|
||||
define('CSRF_SECRET', CSRF_SECRET_PLACEHOLDER);
|
||||
$s1 = getCsrfSecret();
|
||||
$s2 = getCsrfSecret();
|
||||
echo 'len=' . strlen($s1) . "\n";
|
||||
echo 'same=' . ($s1 === $s2 ? 'yes' : 'no') . "\n";
|
||||
echo 'placeholder=' . ($s1 === CSRF_SECRET ? 'yes' : 'no') . "\n";
|
||||
$token = generateCSRFToken();
|
||||
echo 'token=' . (validateCSRFToken($token) ? 'valid' : 'invalid') . "\n";
|
||||
PHP;
|
||||
|
||||
$cmd = escapeshellarg(PHP_BINARY)
|
||||
. ' -d log_errors=1 -d error_log=/dev/stdout -r '
|
||||
. escapeshellarg($snippet)
|
||||
. ' ' . escapeshellarg($securityFile);
|
||||
$fallbackOutput = function_exists('shell_exec') ? shell_exec($cmd) : null;
|
||||
|
||||
if ($fallbackOutput === null) {
|
||||
test_record(false, 'getCsrfSecret : sous-processus de test du repli éphémère (shell_exec indisponible)');
|
||||
} else {
|
||||
assertContains(
|
||||
'SECURITY CRITICAL',
|
||||
$fallbackOutput,
|
||||
'getCsrfSecret enregistre un avertissement critique avec la valeur par défaut'
|
||||
);
|
||||
assertContains(
|
||||
'CSRF_SECRET',
|
||||
$fallbackOutput,
|
||||
'l\'avertissement critique mentionne CSRF_SECRET'
|
||||
);
|
||||
assertContains(
|
||||
'len=64',
|
||||
$fallbackOutput,
|
||||
'le secret éphémère fait 64 caractères hexadécimaux (32 octets)'
|
||||
);
|
||||
assertContains(
|
||||
'same=yes',
|
||||
$fallbackOutput,
|
||||
'le secret éphémère est stable durant tout le processus'
|
||||
);
|
||||
assertContains(
|
||||
'placeholder=no',
|
||||
$fallbackOutput,
|
||||
'le secret éphémère diffère de la valeur par défaut'
|
||||
);
|
||||
assertContains(
|
||||
'token=valid',
|
||||
$fallbackOutput,
|
||||
'un token signé avec le secret éphémère est validé dans le même processus'
|
||||
);
|
||||
}
|
||||
|
||||
// --- isValidRemoteUrl (includes/security.php) ---------------------------------
|
||||
|
||||
assertTrue(isValidRemoteUrl('https://peertube.example.com'), 'isValidRemoteUrl accepte une URL HTTPS publique');
|
||||
assertTrue(isValidRemoteUrl('https://kute.o-k-i.net'), 'isValidRemoteUrl accepte une instance Castopod publique');
|
||||
assertTrue(isValidRemoteUrl('https://mizik.o-k-i.net'), 'isValidRemoteUrl accepte une instance Funkwhale publique');
|
||||
assertTrue(isValidRemoteUrl('http://castopod.example.com'), 'isValidRemoteUrl accepte HTTP (développement)');
|
||||
assertFalse(isValidRemoteUrl('ftp://kute.o-k-i.net'), 'isValidRemoteUrl refuse un schéma non HTTP(S)');
|
||||
assertFalse(isValidRemoteUrl('pas-une-url'), 'isValidRemoteUrl refuse une chaîne mal formée');
|
||||
assertFalse(isValidRemoteUrl(''), 'isValidRemoteUrl refuse une chaîne vide');
|
||||
assertFalse(isValidRemoteUrl('https://localhost'), 'isValidRemoteUrl refuse localhost');
|
||||
assertFalse(isValidRemoteUrl('http://127.0.0.1'), 'isValidRemoteUrl refuse 127.0.0.1');
|
||||
assertFalse(isValidRemoteUrl('http://192.168.1.1'), 'isValidRemoteUrl refuse une IP privée (192.168.x)');
|
||||
assertFalse(isValidRemoteUrl('http://10.0.0.5'), 'isValidRemoteUrl refuse une IP privée (10.x)');
|
||||
assertFalse(isValidRemoteUrl('http://172.16.0.1'), 'isValidRemoteUrl refuse une IP privée (172.16.x)');
|
||||
assertFalse(
|
||||
isValidRemoteUrl('http://169.254.169.254/latest/meta-data'),
|
||||
'isValidRemoteUrl refuse l\'IP de métadonnées cloud (link-local)'
|
||||
);
|
||||
assertFalse(
|
||||
isValidRemoteUrl('https://metadata.google.internal'),
|
||||
'isValidRemoteUrl refuse metadata.google.internal'
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user