mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
aurweb.asgi: add security headers middleware
This commit introduces a middleware function which adds the following security headers to each response: - Content-Security-Policy - This includes a new `nonce`, which is tied to a user via authentication middleware. Both an anonymous user and an authenticated user recieve their own random nonces. - X-Content-Type-Options - Referrer-Policy - X-Frame-Options They are then tested for existence in test/test_routes.py. Note: The overcomplicated-looking asyncio behavior in the middleware function is used to avoid a warning about the old coroutine awaits being deprecated. See https://docs.python.org/3/library/asyncio-task.html#asyncio.wait for more detail. Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
13456fea1e
commit
865c414504
6 changed files with 106 additions and 3 deletions
|
@ -1,7 +1,9 @@
|
|||
import re
|
||||
import urllib.parse
|
||||
|
||||
from http import HTTPStatus
|
||||
|
||||
import lxml.etree
|
||||
import pytest
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
@ -39,6 +41,24 @@ def test_index():
|
|||
assert response.status_code == int(HTTPStatus.OK)
|
||||
|
||||
|
||||
def test_index_security_headers():
|
||||
""" Check for the existence of CSP, XCTO, XFO and RP security headers.
|
||||
|
||||
CSP: Content-Security-Policy
|
||||
XCTO: X-Content-Type-Options
|
||||
RP: Referrer-Policy
|
||||
XFO: X-Frame-Options
|
||||
"""
|
||||
# Use `with` to trigger FastAPI app events.
|
||||
with client as req:
|
||||
response = req.get("/")
|
||||
assert response.status_code == int(HTTPStatus.OK)
|
||||
assert response.headers.get("Content-Security-Policy") is not None
|
||||
assert response.headers.get("X-Content-Type-Options") == "nosniff"
|
||||
assert response.headers.get("Referrer-Policy") == "same-origin"
|
||||
assert response.headers.get("X-Frame-Options") == "SAMEORIGIN"
|
||||
|
||||
|
||||
def test_favicon():
|
||||
""" Test the favicon route at '/favicon.ico'. """
|
||||
response1 = client.get("/static/images/favicon.ico")
|
||||
|
@ -106,3 +126,25 @@ def test_error_messages():
|
|||
response2 = client.get("/raisefivethree")
|
||||
assert response1.status_code == int(HTTPStatus.NOT_FOUND)
|
||||
assert response2.status_code == int(HTTPStatus.SERVICE_UNAVAILABLE)
|
||||
|
||||
|
||||
def test_nonce_csp():
|
||||
with client as request:
|
||||
response = request.get("/")
|
||||
data = response.headers.get("Content-Security-Policy")
|
||||
nonce = next(field for field in data.split("; ") if "nonce" in field)
|
||||
match = re.match(r"^script-src .*'nonce-([a-fA-F0-9]{8})' .*$", nonce)
|
||||
nonce = match.group(1)
|
||||
assert nonce is not None and len(nonce) == 8
|
||||
|
||||
parser = lxml.etree.HTMLParser(recover=True)
|
||||
root = lxml.etree.fromstring(response.text, parser=parser)
|
||||
|
||||
nonce_verified = False
|
||||
scripts = root.xpath("//script")
|
||||
for script in scripts:
|
||||
if script.text is not None:
|
||||
assert "nonce" in script.keys()
|
||||
if not (nonce_verified := (script.get("nonce") == nonce)):
|
||||
break
|
||||
assert nonce_verified is True
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue