fix: CSP compliance for inline styles and scripts

This commit is contained in:
2026-07-27 01:54:57 +04:00
parent 4778f0e9e3
commit df55445480
4 changed files with 133 additions and 4 deletions
+40
View File
@@ -18,6 +18,11 @@
border: 0 !important;
}
/* Masquage d'éléments (remplace les attributs style="display: none;" bloqués par la CSP) */
.is-hidden {
display: none !important;
}
:root {
--primary-red: #FF0000;
--primary-green: #008000;
@@ -3576,3 +3581,38 @@ i.icon-mastodon,
text-align: center;
margin: 4px 0 10px;
}
/* Pages d'erreur (404.php / 500.php) */
.error-page {
text-align: center;
padding: 80px 20px;
}
.error-page .error-code {
font-size: 4.5rem;
font-weight: bold;
line-height: 1;
color: var(--primary-red);
margin-bottom: 10px;
}
.error-page h1 {
margin-bottom: 15px;
}
.error-page > p:not(.error-code):not(.error-actions) {
max-width: 600px;
margin: 0 auto 25px;
}
.error-page .error-home-link {
display: inline-block;
padding: 10px 25px;
background-color: var(--primary-red);
color: #ffffff;
border-radius: 5px;
}
.error-page .error-home-link:hover {
opacity: 0.85;
}
+1 -1
View File
@@ -74,7 +74,7 @@
<button id="theme-toggle" class="icon-button" aria-label="Basculer entre mode clair et sombre" title="Changer le thème">
<i class="fas fa-sun" aria-hidden="true"></i>
</button>
<button id="install-pwa" class="icon-button install-pwa-button" style="display: none;" nonce="<?php echo getCspNonce(); ?>" aria-label="Installer l'application">
<button id="install-pwa" class="icon-button install-pwa-button is-hidden" aria-label="Installer l'application">
<i class="fas fa-download" aria-hidden="true"></i>
</button>
<button class="mobile-menu-toggle" aria-expanded="false" aria-controls="mobile-menu" aria-label="Ouvrir le menu de navigation">
+3 -3
View File
@@ -55,7 +55,7 @@ function addPWAScripts() {
// Afficher le bouton d'installation s'il existe
if (installButton) {
installButton.style.display = 'block';
installButton.classList.remove('is-hidden');
installButton.addEventListener('click', function() {
deferredPrompt.prompt();
deferredPrompt.userChoice.then(function(choiceResult) {
@@ -63,7 +63,7 @@ function addPWAScripts() {
console.log('PWA installée');
}
deferredPrompt = null;
installButton.style.display = 'none';
installButton.classList.add('is-hidden');
});
});
}
@@ -73,7 +73,7 @@ function addPWAScripts() {
window.addEventListener('appinstalled', function() {
console.log('PWA installée avec succès');
if (installButton) {
installButton.style.display = 'none';
installButton.classList.add('is-hidden');
}
});
</script>
+89
View File
@@ -0,0 +1,89 @@
"""Tests E2E de conformité CSP : page de dons, anti-flash de thème et bouton PWA.
Couvre les corrections COR-4/COR-7 de l'audit : scripts inline noncés,
remplacement des `onclick` par des addEventListener, et de
`style="display: none;"` par la classe utilitaire `.is-hidden`.
"""
import pytest
from playwright.sync_api import expect
def _aller_page_dons(page, base_url):
"""Charge dons.php ; saute le test si les dons sont désactivés sur l'instance."""
reponse = page.goto(base_url + "/dons.php", wait_until="domcontentloaded")
if reponse is None or reponse.status != 200:
pytest.skip(f"dons.php indisponible (HTTP {reponse.status if reponse else '?'})")
def _collecter_erreurs_console(page):
"""Installe la collecte des erreurs console/JS ; retourne la liste à vérifier."""
erreurs = []
page.on("console", lambda msg: erreurs.append(msg.text) if msg.type == "error" else None)
page.on("pageerror", lambda exc: erreurs.append(str(exc)))
return erreurs
def test_dons_scripts_inline_ont_un_nonce(page, base_url):
"""Tous les scripts inline de dons.php portent le nonce CSP (COR-7)."""
_aller_page_dons(page, base_url)
sans_nonce = page.locator("script:not([src]):not([nonce])").evaluate_all(
"(els) => els.map(e => e.outerHTML.slice(0, 80))"
)
assert sans_nonce == [], f"Scripts inline sans nonce : {sans_nonce}"
onclick = page.locator("[onclick]").count()
assert onclick == 0, f"{onclick} élément(s) avec un attribut onclick (bloqué par la CSP)"
def test_dons_anti_flash_theme_sans_erreur(page, base_url):
"""Le script anti-flash noncé s'exécute : thème sombre appliqué dès le chargement."""
page.add_init_script("localStorage.setItem('theme', 'dark');")
erreurs = _collecter_erreurs_console(page)
_aller_page_dons(page, base_url)
assert page.locator("html").get_attribute("data-theme") == "dark", (
"data-theme absent : le script anti-flash a été bloqué par la CSP"
)
assert erreurs == [], f"Erreurs console sur dons.php : {erreurs}"
def test_dons_onglets_stripe(page, base_url):
"""Les onglets Don ponctuel / Don mensuel fonctionnent via addEventListener."""
_aller_page_dons(page, base_url)
onglets = page.locator(".donation-tabs .tab-btn")
if onglets.count() == 0:
pytest.skip("Stripe désactivé sur cette instance (STRIPE_ENABLED=false)")
onglet_mensuel = page.locator('.tab-btn[data-tab="monthly"]')
onglet_ponctuel = page.locator('.tab-btn[data-tab="onetime"]')
# État initial : don ponctuel actif
expect(page.locator("#onetime-tab")).to_have_class("tab-content active")
expect(page.locator("#monthly-tab")).to_have_class("tab-content")
# dispatch_event plutôt que click : les polices chargées depuis cdnjs décalent
# la mise en page et rendent le clic aux coordonnées intermittent. Le but est
# de vérifier le câblage addEventListener (bloqué avant la correction CSP).
onglet_mensuel.dispatch_event("click")
expect(page.locator("#monthly-tab")).to_have_class("tab-content active")
expect(page.locator("#onetime-tab")).to_have_class("tab-content")
expect(onglet_mensuel).to_have_class("tab-btn active")
# Retour au don ponctuel
onglet_ponctuel.dispatch_event("click")
expect(page.locator("#onetime-tab")).to_have_class("tab-content active")
expect(page.locator("#monthly-tab")).to_have_class("tab-content")
def test_bouton_install_pwa_utilise_is_hidden(page, base_url):
"""Le bouton d'installation PWA est masqué par .is-hidden, sans style inline (COR-4)."""
_aller_page_dons(page, base_url)
bouton = page.locator("#install-pwa")
assert bouton.count() == 1, "Bouton #install-pwa absent du header"
assert bouton.get_attribute("style") is None, "Attribut style résiduel (bloqué par la CSP)"
assert bouton.get_attribute("nonce") is None, "Attribut nonce résiduel sur un non-script"
assert bouton.evaluate("(e) => e.classList.contains('is-hidden')")
assert not bouton.is_visible(), "Le bouton PWA devrait être masqué sans beforeinstallprompt"