Skip to content

Commit

Permalink
Apply nonces to CSP in central module
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasjuhrich committed Oct 7, 2023
1 parent fa2dfb9 commit c663224
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 38 deletions.
11 changes: 2 additions & 9 deletions sipa/blueprints/usersuite.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
SubnetFull,
)
from sipa.model.misc import PaymentDetails
from sipa.utils.graph_utils import NonceInfo
from sipa.utils.csp import NonceInfo

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -126,14 +126,7 @@ def index():
return resp

assert isinstance(nonce_info, NonceInfo)
script_nonces_str = " ".join(f"'nonce-{n}'" for n in nonce_info.script_nonces)
# NOTE when we do this on other occasions as well, find a way to stop hard-coding
# the rest of our `script_src` CSP and find a more flexible approach
resp.content_security_policy.script_src = (
f"'self' {script_nonces_str} https://status.agdsn.net"
)
style_nonces_str = " ".join(f"'nonce-{n}'" for n in nonce_info.style_nonces)
resp.content_security_policy.style_src = f"'self' {style_nonces_str}"
nonce_info.apply_to_csp(resp.content_security_policy)
return resp


Expand Down
7 changes: 1 addition & 6 deletions sipa/initialization.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import typing as t
import logging
import logging.config
import os
Expand All @@ -24,6 +23,7 @@
from sipa.session import SeparateLocaleCookieSessionInterface
from sipa.utils import url_self, support_hotline_available, meetingcal
from sipa.utils.babel_utils import get_weekday
from sipa.utils.csp import ensure_items
from sipa.utils.git_utils import init_repo, update_repo
from sipa.utils.graph_utils import generate_traffic_chart, provide_render_function

Expand Down Expand Up @@ -259,8 +259,3 @@ def ensure_csp(r: Response) -> Response:
csp.worker_src = ensure_items(csp.worker_src, ("'none'",))
# there doesn't seem to be a good way to set `upgrade-insecure-requests`
return r


def ensure_items(current_items: str | None, items: t.Iterable[str]) -> str:
_cur = set(current_items.split()) if current_items else set()
return " ".join(_cur | set(items))
41 changes: 41 additions & 0 deletions sipa/utils/csp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import secrets
import typing as t
from dataclasses import dataclass, field

from werkzeug.datastructures import ContentSecurityPolicy


def generate_nonce() -> str:
return secrets.token_hex(32)


def ensure_items(current_items: str | None, items: t.Iterable[str]) -> str:
_cur = set(current_items.split()) if current_items else set()
return " ".join(_cur | set(items))


@dataclass(frozen=True)
class NonceInfo:
"""struct to remember which nonces have been generated for inline scripts"""

style_nonces: list[str] = field(default_factory=list)

script_nonces: list[str] = field(default_factory=list)

def add_style_nonce(self) -> str:
self.style_nonces.append(n := generate_nonce())
return n

def add_script_nonce(self) -> str:
self.script_nonces.append(n := generate_nonce())
return n

def apply_to_csp(self, csp: ContentSecurityPolicy) -> ContentSecurityPolicy:
"""Add nonces to the CSP object"""
csp.script_src = ensure_items(
csp.script_src, (f"'nonce-{n}'" for n in self.script_nonces)
)
csp.style_src = ensure_items(
csp.style_src, (f"'nonce-{n}'" for n in self.style_nonces)
)
return csp
24 changes: 1 addition & 23 deletions sipa/utils/graph_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import secrets
from dataclasses import field, dataclass

import pygal
from flask_babel import gettext
from pygal import Graph
Expand All @@ -10,6 +7,7 @@
from sipa.units import (format_as_traffic, max_divisions,
reduce_by_base)
from sipa.utils.babel_utils import get_weekday
from sipa.utils.csp import NonceInfo


def rgb_string(r, g, b):
Expand Down Expand Up @@ -46,26 +44,6 @@ def default_chart(chart_type, title, inline=True, **kwargs):
)


def generate_nonce() -> str:
return secrets.token_hex(32)


@dataclass(frozen=True)
class NonceInfo:
"""struct to remember which nonces have been generated for inline scripts"""

style_nonces: list[str] = field(default_factory=list)
script_nonces: list[str] = field(default_factory=list)

def add_style_nonce(self) -> str:
self.style_nonces.append(n := generate_nonce())
return n

def add_script_nonce(self) -> str:
self.script_nonces.append(n := generate_nonce())
return n


def generate_traffic_chart(traffic_data: list[dict], inline: bool = True) -> Graph:
"""Create a graph object from the input traffic data with pygal.
If inline is set, the chart is being passed the option to not add an XML
Expand Down

0 comments on commit c663224

Please sign in to comment.