102 lines
3.0 KiB
Python
102 lines
3.0 KiB
Python
import re
|
|
|
|
import pytest
|
|
|
|
from apps.blog.models import ArticleIndexPage, ArticlePage
|
|
from apps.blog.tests.factories import AuthorFactory
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_security_headers_present(client, home_page):
|
|
resp = client.get("/")
|
|
assert resp.status_code == 200
|
|
assert "Content-Security-Policy" in resp
|
|
assert "Permissions-Policy" in resp
|
|
assert "unsafe-inline" not in resp["Content-Security-Policy"]
|
|
assert "script-src" in resp["Content-Security-Policy"]
|
|
assert resp["X-Frame-Options"] == "SAMEORIGIN"
|
|
assert "strict-origin-when-cross-origin" in resp["Referrer-Policy"]
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_csp_nonce_applied_to_inline_script(client, home_page):
|
|
resp = client.get("/")
|
|
csp = resp["Content-Security-Policy"]
|
|
match = re.search(r"nonce-([^' ;]+)", csp)
|
|
assert match
|
|
nonce = match.group(1)
|
|
html = resp.content.decode()
|
|
assert f'nonce="{nonce}"' in html
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_robots_disallows_cms_and_contains_sitemap(client):
|
|
resp = client.get("/robots.txt")
|
|
body = resp.content.decode()
|
|
assert resp.status_code == 200
|
|
assert "Disallow: /cms/" in body
|
|
assert "Sitemap:" in body
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_admin_obscured_path_redirects_to_cms(client):
|
|
resp = client.get("/admin/")
|
|
assert resp.status_code == 302
|
|
assert resp["Location"] == "/cms/"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_article_comment_form_contains_csrf_token(client, home_page):
|
|
index = ArticleIndexPage(title="Articles", slug="articles")
|
|
home_page.add_child(instance=index)
|
|
author = AuthorFactory()
|
|
article = ArticlePage(
|
|
title="CSRF Article",
|
|
slug="csrf-article",
|
|
author=author,
|
|
summary="summary",
|
|
body=[("rich_text", "<p>Body</p>")],
|
|
)
|
|
index.add_child(instance=article)
|
|
article.save_revision().publish()
|
|
|
|
resp = client.get("/articles/csrf-article/")
|
|
html = resp.content.decode()
|
|
assert resp.status_code == 200
|
|
assert "csrfmiddlewaretoken" in html
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_consent_rejects_open_redirect(client, home_page):
|
|
resp = client.post(
|
|
"/consent/",
|
|
{"reject_all": "1"},
|
|
HTTP_REFERER="https://evil.example.com/phish",
|
|
)
|
|
assert resp.status_code == 302
|
|
assert resp["Location"] == "/"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_article_json_ld_script_has_csp_nonce(client, home_page):
|
|
index = ArticleIndexPage(title="Articles", slug="articles")
|
|
home_page.add_child(instance=index)
|
|
author = AuthorFactory()
|
|
article = ArticlePage(
|
|
title="Nonce Article",
|
|
slug="nonce-article",
|
|
author=author,
|
|
summary="summary",
|
|
body=[("rich_text", "<p>Body</p>")],
|
|
)
|
|
index.add_child(instance=article)
|
|
article.save_revision().publish()
|
|
|
|
resp = client.get("/articles/nonce-article/")
|
|
csp = resp["Content-Security-Policy"]
|
|
match = re.search(r"nonce-([^' ;]+)", csp)
|
|
assert match
|
|
nonce = match.group(1)
|
|
html = resp.content.decode()
|
|
assert f'type="application/ld+json" nonce="{nonce}"' in html
|