158 lines
7.8 KiB
PHP
158 lines
7.8 KiB
PHP
<?php
|
|||
|
|
/**
|
||
|
|
* Tests unitaires pour includes/security.php
|
||
|
|
* (et les validateurs d'URL de includes/config.php)
|
||
|
|
*/
|
||
|
|
|
||
|
|
// --- validateVideoId --------------------------------------------------------
|
||
|
|
|
||
|
|
assertEquals(
|
||
|
|
'9c5de94d-8e1a-4b3c-9d2e-1234567890ab',
|
||
|
|
validateVideoId('9c5de94d-8e1a-4b3c-9d2e-1234567890ab'),
|
||
|
|
'validateVideoId accepte un UUID valide'
|
||
|
|
);
|
||
|
|
assertEquals(
|
||
|
|
'9C5DE94D-8E1A-4B3C-9D2E-1234567890AB',
|
||
|
|
validateVideoId('9C5DE94D-8E1A-4B3C-9D2E-1234567890AB'),
|
||
|
|
'validateVideoId accepte un UUID en majuscules'
|
||
|
|
);
|
||
|
|
assertEquals(
|
||
|
|
'9c5de94d-8e1a-4b3c-9d2e-1234567890ab',
|
||
|
|
validateVideoId(' 9c5de94d-8e1a-4b3c-9d2e-1234567890ab '),
|
||
|
|
'validateVideoId nettoie les espaces autour de l\'UUID'
|
||
|
|
);
|
||
|
|
assertFalse(validateVideoId(''), 'validateVideoId refuse une chaîne vide');
|
||
|
|
assertFalse(validateVideoId('pas-un-uuid'), 'validateVideoId refuse une chaîne quelconque');
|
||
|
|
assertFalse(
|
||
|
|
validateVideoId('9c5de94d-8e1a-4b3c-1234567890ab'),
|
||
|
|
'validateVideoId refuse un UUID tronqué'
|
||
|
|
);
|
||
|
|
|
||
|
|
// --- validateSearchQuery ----------------------------------------------------
|
||
|
|
|
||
|
|
assertEquals('indépendance', validateSearchQuery('indépendance'), 'validateSearchQuery conserve les accents');
|
||
|
|
assertEquals(
|
||
|
|
'script',
|
||
|
|
validateSearchQuery('<script>'),
|
||
|
|
'validateSearchQuery supprime les chevrons'
|
||
|
|
);
|
||
|
|
assertEquals(
|
||
|
|
'recherche btest/b',
|
||
|
|
validateSearchQuery('recherche <b>test</b>'),
|
||
|
|
'validateSearchQuery supprime les caractères dangereux <>"\''
|
||
|
|
);
|
||
|
|
assertEquals('terme', validateSearchQuery(' terme '), 'validateSearchQuery nettoie les espaces');
|
||
|
|
assertFalse(validateSearchQuery(''), 'validateSearchQuery refuse une chaîne vide');
|
||
|
|
assertFalse(
|
||
|
|
validateSearchQuery(str_repeat('a', 201)),
|
||
|
|
'validateSearchQuery refuse plus de 200 caractères'
|
||
|
|
);
|
||
|
|
assertEquals(
|
||
|
|
str_repeat('a', 200),
|
||
|
|
validateSearchQuery(str_repeat('a', 200)),
|
||
|
|
'validateSearchQuery accepte exactement 200 caractères'
|
||
|
|
);
|
||
|
|
|
||
|
|
// --- validateCategoryId -----------------------------------------------------
|
||
|
|
|
||
|
|
assertEquals(1, validateCategoryId(1), 'validateCategoryId accepte la borne basse 1');
|
||
|
|
assertEquals(20, validateCategoryId(20), 'validateCategoryId accepte la borne haute 20');
|
||
|
|
assertEquals(5, validateCategoryId('5'), 'validateCategoryId convertit une chaîne numérique');
|
||
|
|
assertFalse(validateCategoryId(0), 'validateCategoryId refuse 0');
|
||
|
|
assertFalse(validateCategoryId(21), 'validateCategoryId refuse 21');
|
||
|
|
assertFalse(validateCategoryId(-3), 'validateCategoryId refuse un nombre négatif');
|
||
|
|
assertFalse(validateCategoryId('abc'), 'validateCategoryId refuse une chaîne non numérique');
|
||
|
|
|
||
|
|
// --- validatePageNumber -----------------------------------------------------
|
||
|
|
|
||
|
|
assertEquals(1, validatePageNumber(0), 'validatePageNumber remonte 0 à 1');
|
||
|
|
assertEquals(1, validatePageNumber(-5), 'validatePageNumber remonte un négatif à 1');
|
||
|
|
assertEquals(3, validatePageNumber(3), 'validatePageNumber conserve une page valide');
|
||
|
|
assertEquals(2, validatePageNumber('2'), 'validatePageNumber convertit une chaîne numérique');
|
||
|
|
assertEquals(1, validatePageNumber('abc'), 'validatePageNumber ramène une chaîne non numérique à 1');
|
||
|
|
|
||
|
|
// --- generateCSRFToken / validateCSRFToken ----------------------------------
|
||
|
|
|
||
|
|
$token = generateCSRFToken();
|
||
|
|
assertContains(':', $token, 'generateCSRFToken produit le format "timestamp:hash"');
|
||
|
|
|
||
|
|
[$timestamp, $hash] = explode(':', $token, 2);
|
||
|
|
assertTrue(ctype_digit($timestamp), 'generateCSRFToken : la partie timestamp est numérique');
|
||
|
|
assertEquals(
|
||
|
|
hash_hmac('sha256', $timestamp, CSRF_SECRET),
|
||
|
|
$hash,
|
||
|
|
'generateCSRFToken : le hash est un HMAC-SHA256 du timestamp avec CSRF_SECRET'
|
||
|
|
);
|
||
|
|
assertTrue(validateCSRFToken($token), 'validateCSRFToken accepte un token fraîchement généré');
|
||
|
|
|
||
|
|
assertFalse(validateCSRFToken(''), 'validateCSRFToken refuse une chaîne vide');
|
||
|
|
assertFalse(validateCSRFToken('sans-deux-points'), 'validateCSRFToken refuse un token sans séparateur');
|
||
|
|
assertFalse(validateCSRFToken('abc:' . $hash), 'validateCSRFToken refuse un timestamp non numérique');
|
||
|
|
assertFalse(
|
||
|
|
validateCSRFToken($timestamp . ':' . hash_hmac('sha256', $timestamp, 'autre-secret')),
|
||
|
|
'validateCSRFToken refuse un hash signé avec un autre secret'
|
||
|
|
);
|
||
|
|
assertFalse(
|
||
|
|
validateCSRFToken($timestamp . ':0' . $hash),
|
||
|
|
'validateCSRFToken refuse un hash modifié'
|
||
|
|
);
|
||
|
|
|
||
|
|
// Token expiré : horodatage d'il y a 2 heures (limite : 1 heure)
|
||
|
|
$oldTimestamp = (string) (time() - 7200);
|
||
|
|
$oldToken = $oldTimestamp . ':' . hash_hmac('sha256', $oldTimestamp, CSRF_SECRET);
|
||
|
|
assertFalse(validateCSRFToken($oldToken), 'validateCSRFToken refuse un token expiré (> 1 heure)');
|
||
|
|
|
||
|
|
// Timestamp trop loin dans le futur (l'écart absolu est également plafonné)
|
||
|
|
$futureTimestamp = (string) (time() + 7200);
|
||
|
|
$futureToken = $futureTimestamp . ':' . hash_hmac('sha256', $futureTimestamp, CSRF_SECRET);
|
||
|
|
assertFalse(validateCSRFToken($futureToken), 'validateCSRFToken refuse un timestamp trop futur');
|
||
|
|
|
||
|
|
// --- isValidPeerTubeUrl (includes/config.php) -------------------------------
|
||
|
|
|
||
|
|
assertTrue(isValidPeerTubeUrl('https://peertube.example.com'), 'isValidPeerTubeUrl accepte une URL HTTPS publique');
|
||
|
|
assertTrue(isValidPeerTubeUrl('http://peertube.example.com'), 'isValidPeerTubeUrl accepte HTTP (développement)');
|
||
|
|
assertTrue(isValidPeerTubeUrl('https://8.8.8.8'), 'isValidPeerTubeUrl accepte une IP publique');
|
||
|
|
assertFalse(isValidPeerTubeUrl('ftp://example.com'), 'isValidPeerTubeUrl refuse un schéma non HTTP(S)');
|
||
|
|
assertFalse(isValidPeerTubeUrl('pas-une-url'), 'isValidPeerTubeUrl refuse une chaîne mal formée');
|
||
|
|
assertFalse(isValidPeerTubeUrl('https://localhost'), 'isValidPeerTubeUrl refuse localhost');
|
||
|
|
assertFalse(isValidPeerTubeUrl('http://127.0.0.1'), 'isValidPeerTubeUrl refuse 127.0.0.1');
|
||
|
|
assertFalse(isValidPeerTubeUrl('http://192.168.1.1'), 'isValidPeerTubeUrl refuse une IP privée');
|
||
|
|
assertFalse(isValidPeerTubeUrl('http://10.0.0.5'), 'isValidPeerTubeUrl refuse une IP privée (10.x)');
|
||
|
|
assertFalse(
|
||
|
|
isValidPeerTubeUrl('https://metadata.google.internal'),
|
||
|
|
'isValidPeerTubeUrl refuse metadata.google.internal'
|
||
|
|
);
|
||
|
|
|
||
|
|
// --- isValidApiEndpoint (includes/config.php) -------------------------------
|
||
|
|
|
||
|
|
assertTrue(isValidApiEndpoint('videos'), 'isValidApiEndpoint accepte "videos"');
|
||
|
|
assertTrue(isValidApiEndpoint('videos/categories'), 'isValidApiEndpoint accepte "videos/categories"');
|
||
|
|
assertTrue(isValidApiEndpoint('search/videos'), 'isValidApiEndpoint accepte "search/videos"');
|
||
|
|
assertTrue(isValidApiEndpoint('accounts'), 'isValidApiEndpoint accepte "accounts"');
|
||
|
|
assertTrue(
|
||
|
|
isValidApiEndpoint('videos/9c5de94d-8e1a-4b3c-9d2e-1234567890ab'),
|
||
|
|
'isValidApiEndpoint accepte un endpoint vidéo dynamique'
|
||
|
|
);
|
||
|
|
assertTrue(
|
||
|
|
isValidApiEndpoint('videos/abc/comment-threads'),
|
||
|
|
'isValidApiEndpoint accepte les fils de commentaires'
|
||
|
|
);
|
||
|
|
assertTrue(
|
||
|
|
isValidApiEndpoint('accounts/annu_kute_ced/videos'),
|
||
|
|
'isValidApiEndpoint accepte les vidéos d\'un compte'
|
||
|
|
);
|
||
|
|
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');
|
||
|
|
assertFalse(isValidApiEndpoint('wp-admin'), 'isValidApiEndpoint refuse un endpoint hors liste blanche');
|
||
|
|
assertFalse(isValidApiEndpoint('videos.json'), 'isValidApiEndpoint refuse les caractères non autorisés (.)');
|
||
|
|
assertFalse(isValidApiEndpoint('videos?count=1'), 'isValidApiEndpoint refuse une query string');
|
||
|
|
assertFalse(isValidApiEndpoint(''), 'isValidApiEndpoint refuse une chaîne vide');
|
||
|
|
|
||
|
|
// --- getCspNonce ------------------------------------------------------------
|
||
|
|
|
||
|
|
$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');
|