Skip to content

Commit

Permalink
Replace HttpResponse type with HttpResponseBase
Browse files Browse the repository at this point in the history
  • Loading branch information
robhudson committed Jun 28, 2024
1 parent c2a4317 commit 794c819
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 44 deletions.
3 changes: 2 additions & 1 deletion csp/checks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import pprint
from typing import Dict, Tuple, Any, Optional, Sequence, TYPE_CHECKING, List
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple

from django.conf import settings
from django.core.checks import Error
Expand Down
3 changes: 2 additions & 1 deletion csp/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations
from typing import Dict, Literal, TYPE_CHECKING

from typing import TYPE_CHECKING, Dict, Literal

if TYPE_CHECKING:
from django.http import HttpRequest
Expand Down
9 changes: 5 additions & 4 deletions csp/contrib/rate_limiting.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
from __future__ import annotations
from typing import TYPE_CHECKING

import random
from typing import TYPE_CHECKING

from django.conf import settings

from csp.middleware import CSPMiddleware
from csp.utils import build_policy

if TYPE_CHECKING:
from django.http import HttpRequest, HttpResponse
from django.http import HttpRequest, HttpResponseBase


class RateLimitedCSPMiddleware(CSPMiddleware):
"""A CSP middleware that rate-limits the number of violation reports sent
to report-uri by excluding it from some requests."""

def build_policy(self, request: HttpRequest, response: HttpResponse) -> str:
def build_policy(self, request: HttpRequest, response: HttpResponseBase) -> str:
config = getattr(response, "_csp_config", None)
update = getattr(response, "_csp_update", None)
replace = getattr(response, "_csp_replace", {})
Expand All @@ -33,7 +34,7 @@ def build_policy(self, request: HttpRequest, response: HttpResponse) -> str:

return build_policy(config=config, update=update, replace=replace, nonce=nonce)

def build_policy_ro(self, request: HttpRequest, response: HttpResponse) -> str:
def build_policy_ro(self, request: HttpRequest, response: HttpResponseBase) -> str:
config = getattr(response, "_csp_config_ro", None)
update = getattr(response, "_csp_update_ro", None)
replace = getattr(response, "_csp_replace_ro", {})
Expand Down
20 changes: 11 additions & 9 deletions csp/decorators.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from functools import wraps
from typing import Callable, Optional, Any, Dict, List
from django.http import HttpRequest, HttpResponse
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional

# A generic Django view function
_VIEW_T = Callable[[HttpRequest], HttpResponse]
_VIEW_DECORATOR_T = Callable[[_VIEW_T], _VIEW_T]
if TYPE_CHECKING:
from django.http import HttpRequest, HttpResponseBase

# A generic Django view function
_VIEW_T = Callable[[HttpRequest], HttpResponseBase]
_VIEW_DECORATOR_T = Callable[[_VIEW_T], _VIEW_T]


def csp_exempt(REPORT_ONLY: Optional[bool] = None) -> _VIEW_DECORATOR_T:
Expand All @@ -18,7 +20,7 @@ def csp_exempt(REPORT_ONLY: Optional[bool] = None) -> _VIEW_DECORATOR_T:

def decorator(f: _VIEW_T) -> _VIEW_T:
@wraps(f)
def _wrapped(*a: Any, **kw: Any) -> HttpResponse:
def _wrapped(*a: Any, **kw: Any) -> HttpResponseBase:
resp = f(*a, **kw)
if REPORT_ONLY:
setattr(resp, "_csp_exempt_ro", True)
Expand All @@ -44,7 +46,7 @@ def csp_update(config: Optional[Dict[str, Any]] = None, REPORT_ONLY: bool = Fals

def decorator(f: _VIEW_T) -> _VIEW_T:
@wraps(f)
def _wrapped(*a: Any, **kw: Any) -> HttpResponse:
def _wrapped(*a: Any, **kw: Any) -> HttpResponseBase:
resp = f(*a, **kw)
if REPORT_ONLY:
setattr(resp, "_csp_update_ro", config)
Expand All @@ -63,7 +65,7 @@ def csp_replace(config: Optional[Dict[str, Any]] = None, REPORT_ONLY: bool = Fal

def decorator(f: _VIEW_T) -> _VIEW_T:
@wraps(f)
def _wrapped(*a: Any, **kw: Any) -> HttpResponse:
def _wrapped(*a: Any, **kw: Any) -> HttpResponseBase:
resp = f(*a, **kw)
if REPORT_ONLY:
setattr(resp, "_csp_replace_ro", config)
Expand All @@ -87,7 +89,7 @@ def csp(config: Optional[Dict[str, Any]] = None, REPORT_ONLY: bool = False, **kw

def decorator(f: _VIEW_T) -> _VIEW_T:
@wraps(f)
def _wrapped(*a: Any, **kw: Any) -> HttpResponse:
def _wrapped(*a: Any, **kw: Any) -> HttpResponseBase:
resp = f(*a, **kw)
if REPORT_ONLY:
setattr(resp, "_csp_config_ro", processed_config)
Expand Down
3 changes: 2 additions & 1 deletion csp/extensions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations
from typing import Callable, TYPE_CHECKING, Any

from typing import TYPE_CHECKING, Any, Callable

from jinja2 import nodes
from jinja2.ext import Extension
Expand Down
9 changes: 5 additions & 4 deletions csp/middleware.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations

import base64
import http.client as http_client
import os
Expand All @@ -13,7 +14,7 @@
from csp.utils import build_policy

if TYPE_CHECKING:
from django.http import HttpRequest, HttpResponse
from django.http import HttpRequest, HttpResponseBase


class CSPMiddleware(MiddlewareMixin):
Expand All @@ -39,7 +40,7 @@ def process_request(self, request: HttpRequest) -> None:
nonce = partial(self._make_nonce, request)
setattr(request, "csp_nonce", SimpleLazyObject(nonce))

def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse:
def process_response(self, request: HttpRequest, response: HttpResponseBase) -> HttpResponseBase:
# Check for debug view
exempted_debug_codes = (
http_client.INTERNAL_SERVER_ERROR,
Expand Down Expand Up @@ -72,14 +73,14 @@ def process_response(self, request: HttpRequest, response: HttpResponse) -> Http

return response

def build_policy(self, request: HttpRequest, response: HttpResponse) -> str:
def build_policy(self, request: HttpRequest, response: HttpResponseBase) -> str:
config = getattr(response, "_csp_config", None)
update = getattr(response, "_csp_update", None)
replace = getattr(response, "_csp_replace", None)
nonce = getattr(request, "_csp_nonce", None)
return build_policy(config=config, update=update, replace=replace, nonce=nonce)

def build_policy_ro(self, request: HttpRequest, response: HttpResponse) -> str:
def build_policy_ro(self, request: HttpRequest, response: HttpResponseBase) -> str:
config = getattr(response, "_csp_config_ro", None)
update = getattr(response, "_csp_update_ro", None)
replace = getattr(response, "_csp_replace_ro", None)
Expand Down
4 changes: 3 additions & 1 deletion csp/templatetags/csp.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Optional

from django import template
from django.template.base import token_kwargs

from csp.utils import build_script_tag

if TYPE_CHECKING:
from django.template.base import NodeList, FilterExpression, Token, Parser
from django.template.base import FilterExpression, NodeList, Parser, Token
from django.template.context import Context

register = template.Library()
Expand Down
45 changes: 23 additions & 22 deletions csp/tests/test_decorators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations

from typing import TYPE_CHECKING

import pytest
Expand All @@ -12,14 +13,14 @@
from csp.tests.utils import response

if TYPE_CHECKING:
from django.http import HttpRequest
from django.http import HttpRequest, HttpResponseBase

mw = CSPMiddleware(response())


def test_csp_exempt() -> None:
@csp_exempt()
def view(request: HttpRequest) -> HttpResponse:
def view(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view(RequestFactory().get("/"))
Expand All @@ -29,7 +30,7 @@ def view(request: HttpRequest) -> HttpResponse:

def test_csp_exempt_ro() -> None:
@csp_exempt(REPORT_ONLY=True)
def view(request: HttpRequest) -> HttpResponse:
def view(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view(RequestFactory().get("/"))
Expand All @@ -41,7 +42,7 @@ def view(request: HttpRequest) -> HttpResponse:
def test_csp_update() -> None:
request = RequestFactory().get("/")

def view_without_decorator(request: HttpRequest) -> HttpResponse:
def view_without_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_without_decorator(request)
Expand All @@ -51,7 +52,7 @@ def view_without_decorator(request: HttpRequest) -> HttpResponse:
assert policy_list == ["default-src 'self'", "img-src foo.com"]

@csp_update({"img-src": ["bar.com", NONCE]})
def view_with_decorator(request: HttpRequest) -> HttpResponse:
def view_with_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_with_decorator(request)
Expand All @@ -74,7 +75,7 @@ def view_with_decorator(request: HttpRequest) -> HttpResponse:
def test_csp_update_ro() -> None:
request = RequestFactory().get("/")

def view_without_decorator(request: HttpRequest) -> HttpResponse:
def view_without_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_without_decorator(request)
Expand All @@ -84,7 +85,7 @@ def view_without_decorator(request: HttpRequest) -> HttpResponse:
assert policy_list == ["default-src 'self'", "img-src foo.com"]

@csp_update({"img-src": ["bar.com", NONCE]}, REPORT_ONLY=True)
def view_with_decorator(request: HttpRequest) -> HttpResponse:
def view_with_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_with_decorator(request)
Expand All @@ -107,7 +108,7 @@ def view_with_decorator(request: HttpRequest) -> HttpResponse:
def test_csp_replace() -> None:
request = RequestFactory().get("/")

def view_without_decorator(request: HttpRequest) -> HttpResponse:
def view_without_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_without_decorator(request)
Expand All @@ -117,7 +118,7 @@ def view_without_decorator(request: HttpRequest) -> HttpResponse:
assert policy_list == ["default-src 'self'", "img-src foo.com"]

@csp_replace({"img-src": ["bar.com"]})
def view_with_decorator(request: HttpRequest) -> HttpResponse:
def view_with_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_with_decorator(request)
Expand All @@ -134,7 +135,7 @@ def view_with_decorator(request: HttpRequest) -> HttpResponse:
assert policy_list == ["default-src 'self'", "img-src foo.com"]

@csp_replace({"img-src": None})
def view_removing_directive(request: HttpRequest) -> HttpResponse:
def view_removing_directive(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_removing_directive(request)
Expand All @@ -148,7 +149,7 @@ def view_removing_directive(request: HttpRequest) -> HttpResponse:
def test_csp_replace_ro() -> None:
request = RequestFactory().get("/")

def view_without_decorator(request: HttpRequest) -> HttpResponse:
def view_without_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_without_decorator(request)
Expand All @@ -158,7 +159,7 @@ def view_without_decorator(request: HttpRequest) -> HttpResponse:
assert policy_list == ["default-src 'self'", "img-src foo.com"]

@csp_replace({"img-src": ["bar.com"]}, REPORT_ONLY=True)
def view_with_decorator(request: HttpRequest) -> HttpResponse:
def view_with_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_with_decorator(request)
Expand All @@ -175,7 +176,7 @@ def view_with_decorator(request: HttpRequest) -> HttpResponse:
assert policy_list == ["default-src 'self'", "img-src foo.com"]

@csp_replace({"img-src": None}, REPORT_ONLY=True)
def view_removing_directive(request: HttpRequest) -> HttpResponse:
def view_removing_directive(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_removing_directive(request)
Expand All @@ -188,7 +189,7 @@ def view_removing_directive(request: HttpRequest) -> HttpResponse:
def test_csp() -> None:
request = RequestFactory().get("/")

def view_without_decorator(request: HttpRequest) -> HttpResponse:
def view_without_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_without_decorator(request)
Expand All @@ -198,7 +199,7 @@ def view_without_decorator(request: HttpRequest) -> HttpResponse:
assert policy_list == ["default-src 'self'"]

@csp({"img-src": ["foo.com"], "font-src": ["bar.com"]})
def view_with_decorator(request: HttpRequest) -> HttpResponse:
def view_with_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_with_decorator(request)
Expand All @@ -218,7 +219,7 @@ def view_with_decorator(request: HttpRequest) -> HttpResponse:
def test_csp_ro() -> None:
request = RequestFactory().get("/")

def view_without_decorator(request: HttpRequest) -> HttpResponse:
def view_without_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_without_decorator(request)
Expand All @@ -229,7 +230,7 @@ def view_without_decorator(request: HttpRequest) -> HttpResponse:

@csp({"img-src": ["foo.com"], "font-src": ["bar.com"]}, REPORT_ONLY=True)
@csp({}) # CSP with no directives effectively removes the header.
def view_with_decorator(request: HttpRequest) -> HttpResponse:
def view_with_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_with_decorator(request)
Expand All @@ -251,7 +252,7 @@ def test_csp_string_values() -> None:
request = RequestFactory().get("/")

@csp({"img-src": "foo.com", "font-src": "bar.com"})
def view_with_decorator(request: HttpRequest) -> HttpResponse:
def view_with_decorator(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

response = view_with_decorator(request)
Expand All @@ -268,7 +269,7 @@ def test_csp_exempt_error() -> None:
with pytest.raises(RuntimeError) as excinfo:
# Ignore type error since we're checking for the exception raised for 3.x syntax
@csp_exempt # type: ignore
def view(request: HttpRequest) -> HttpResponse:
def view(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

assert "Incompatible `csp_exempt` decorator usage" in str(excinfo.value)
Expand All @@ -278,7 +279,7 @@ def test_csp_update_error() -> None:
with pytest.raises(RuntimeError) as excinfo:

@csp_update(IMG_SRC="bar.com")
def view(request: HttpRequest) -> HttpResponse:
def view(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

assert "Incompatible `csp_update` decorator arguments" in str(excinfo.value)
Expand All @@ -288,7 +289,7 @@ def test_csp_replace_error() -> None:
with pytest.raises(RuntimeError) as excinfo:

@csp_replace(IMG_SRC="bar.com")
def view(request: HttpRequest) -> HttpResponse:
def view(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

assert "Incompatible `csp_replace` decorator arguments" in str(excinfo.value)
Expand All @@ -298,7 +299,7 @@ def test_csp_error() -> None:
with pytest.raises(RuntimeError) as excinfo:

@csp(IMG_SRC=["bar.com"])
def view(request: HttpRequest) -> HttpResponse:
def view(request: HttpRequest) -> HttpResponseBase:
return HttpResponse()

assert "Incompatible `csp` decorator arguments" in str(excinfo.value)
3 changes: 2 additions & 1 deletion csp/tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Dict, Optional, TYPE_CHECKING, Callable, Any, Tuple
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple

from django.http import HttpResponse
from django.template import Context, Template, engines
Expand Down

0 comments on commit 794c819

Please sign in to comment.