Skip to content

Commit

Permalink
make sure that frontend server does not need key backend for ACME, an…
Browse files Browse the repository at this point in the history
…d make tests more pytest-like
  • Loading branch information
mathiasertl committed Aug 23, 2024
1 parent 9d9c1a3 commit 5492de1
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 109 deletions.
1 change: 1 addition & 0 deletions ca/django_ca/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
# NOTE: No need to add test_* modules, they are included automatically.
pytest.register_assert_rewrite(
"django_ca.tests.base.assertions",
"django_ca.tests.acme.views.assertions",
"django_ca.tests.admin.assertions",
"django_ca.tests.pydantic.base",
)
Expand Down
73 changes: 73 additions & 0 deletions ca/django_ca/tests/acme/views/assertions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# This file is part of django-ca (https://github.com/mathiasertl/django-ca).
#
# django-ca is free software: you can redistribute it and/or modify it under the terms of the GNU General
# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# django-ca is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along with django-ca. If not, see
# <http://www.gnu.org/licenses/>.

"""Assertions for ACME views."""

import re
from typing import TYPE_CHECKING, Optional

from requests.utils import parse_header_links

from django.urls import reverse

from django_ca.models import CertificateAuthority

if TYPE_CHECKING:
from django.test.client import _MonkeyPatchedWSGIResponse as HttpResponse


def assert_link_relations(response: "HttpResponse", ca: CertificateAuthority, **kwargs: str) -> None:
"""Assert Link relations for a given request."""
directory = reverse("django_ca:acme-directory", kwargs={"serial": ca.serial})
kwargs.setdefault("index", response.wsgi_request.build_absolute_uri(directory))

expected = [{"rel": k, "url": v} for k, v in kwargs.items()]
actual = parse_header_links(response["Link"])
assert expected == actual


def assert_acme_problem(
response: "HttpResponse",
typ: str,
status: int,
message: str,
ca: CertificateAuthority,
link_relations: Optional[dict[str, str]] = None,
regex: bool = False,
) -> None:
"""Assert that an HTTP response confirms to an ACME problem report.
.. seealso:: `RFC 8555, section 8 <https://tools.ietf.org/html/rfc8555#section-6.7>`_
"""
link_relations = link_relations or {}
assert response["Content-Type"] == "application/problem+json", response.content
assert_link_relations(response, ca=ca, **link_relations)
data = response.json()
assert data["type"] == f"urn:ietf:params:acme:error:{typ}", f"detail={data['detail']}"
assert data["status"] == status
if regex:
assert re.search(message, data["detail"])
else:
assert data["detail"] == message
assert "Replay-Nonce" in response


def assert_acme_response(
response: "HttpResponse",
ca: CertificateAuthority,
link_relations: Optional[dict[str, str]] = None,
) -> None:
"""Assert basic Acme Response properties (Content-Type & Link header)."""
link_relations = link_relations or {}
assert_link_relations(response, ca, **link_relations)
assert response["Content-Type"] == "application/json"
39 changes: 6 additions & 33 deletions ca/django_ca/tests/acme/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import acme
import acme.jws
import josepy as jose
from requests.utils import parse_header_links

from cryptography.hazmat.primitives.asymmetric.types import CertificateIssuerPrivateKeyTypes
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
Expand All @@ -34,6 +33,10 @@

from django_ca.acme.responses import AcmeResponseUnauthorized
from django_ca.models import AcmeAccount, CertificateAuthority, acme_slug
from django_ca.tests.acme.views.assertions import (
assert_acme_problem,
assert_acme_response,
)
from django_ca.tests.base.constants import CERT_DATA
from django_ca.tests.base.mixins import TestCaseMixin
from django_ca.tests.base.utils import mock_slug, override_tmpcadir
Expand Down Expand Up @@ -85,8 +88,6 @@ def absolute_uri(self, name: str, hostname: Optional[str] = None, **kwargs: Any)
hostname = self.SERVER_NAME
return super().absolute_uri(name, hostname=hostname, **kwargs)

# NOINSPECTION NOTE: PyCharm does not detect mixins as a TestCase
# noinspection PyPep8Naming
def assertAcmeProblem( # pylint: disable=invalid-name
self,
response: "HttpResponse",
Expand All @@ -101,17 +102,7 @@ def assertAcmeProblem( # pylint: disable=invalid-name
.. seealso:: `RFC 8555, section 8 <https://tools.ietf.org/html/rfc8555#section-6.7>`_
"""
link_relations = link_relations or {}
self.assertEqual(response["Content-Type"], "application/problem+json", response.content)
self.assertLinkRelations(response, ca=ca, **link_relations)
data = response.json()
self.assertEqual(data["type"], f"urn:ietf:params:acme:error:{typ}", f"detail={data['detail']}")
self.assertEqual(data["status"], status)
if regex:
self.assertRegex(data["detail"], message)
else:
self.assertEqual(data["detail"], message)
self.assertIn("Replay-Nonce", response)
assert_acme_problem(response, typ, status, message, ca or self.ca, link_relations, regex)

# NOINSPECTION NOTE: PyCharm does not detect mixins as a TestCase
# noinspection PyPep8Naming
Expand All @@ -122,25 +113,7 @@ def assertAcmeResponse( # pylint: disable=invalid-name
link_relations: Optional[dict[str, str]] = None,
) -> None:
"""Assert basic Acme Response properties (Content-Type & Link header)."""
link_relations = link_relations or {}
self.assertLinkRelations(response, ca=ca, **link_relations)
self.assertEqual(response["Content-Type"], "application/json")

# NOINSPECTION NOTE: PyCharm does not detect mixins as a TestCase
# noinspection PyPep8Naming
def assertLinkRelations( # pylint: disable=invalid-name
self, response: "HttpResponse", ca: Optional[CertificateAuthority] = None, **kwargs: str
) -> None:
"""Assert Link relations for a given request."""
if ca is None: # pragma: no branch
ca = self.ca

directory = reverse("django_ca:acme-directory", kwargs={"serial": ca.serial})
kwargs.setdefault("index", response.wsgi_request.build_absolute_uri(directory))

expected = [{"rel": k, "url": v} for k, v in kwargs.items()]
actual = parse_header_links(response["Link"])
self.assertEqual(expected, actual)
assert_acme_response(response, ca or self.ca, link_relations)

# NOINSPECTION NOTE: PyCharm does not detect mixins as a TestCase
# noinspection PyPep8Naming
Expand Down
Loading

0 comments on commit 5492de1

Please sign in to comment.