Skip to content

Commit

Permalink
fix last coverage issues
Browse files Browse the repository at this point in the history
  • Loading branch information
mathiasertl committed Jan 1, 2024
1 parent 7ea8cca commit e9a7649
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 29 deletions.
13 changes: 7 additions & 6 deletions ca/django_ca/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
from django_ca.querysets import CertificateQuerySet
from django_ca.signals import post_issue_cert
from django_ca.typehints import CRLExtensionType, X509CertMixinTypeVar
from django_ca.utils import SERIAL_RE, add_colons, name_for_display
from django_ca.utils import SERIAL_RE, add_colons, format_name_rfc4514, name_for_display

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -840,17 +840,20 @@ def csr_details_view(self, request: HttpRequest) -> JsonResponse:

@property
def name_to_rfc4514_view_name(self) -> str:
"""URL for the CSR details view."""
"""View name of ``name_to_rfc4514_view``."""
return f"{self.model._meta.app_label}_{self.model._meta.verbose_name}_name_to_rfc4514"

def name_to_rfc4514_view(self, request: HttpRequest) -> JsonResponse:
"""Returns details of a CSR request."""
"""API that accepts a serialized x509.Name and converts it to an RFC 4514 string.
This endpoint is called when updating the `relative_name` of a CRL Distribution Points extension.
"""
if not request.user.is_staff or not self.has_change_permission(request):
# NOTE: is_staff is already assured by ModelAdmin, but just to be sure
raise PermissionDenied

name_model = NameModel.model_validate_json(request.body, strict=True)
return JsonResponse({"name": name_model.cryptography.rfc4514_string()})
return JsonResponse({"name": format_name_rfc4514(name_model.cryptography)})

def get_urls(self) -> List[URLPattern]:
# Remove the delete action from the URLs
Expand Down Expand Up @@ -1066,8 +1069,6 @@ def save_model( # type: ignore[override]
continue
if oid == ExtensionOID.SUBJECT_ALTERNATIVE_NAME: # already handled above
continue
if oid == ExtensionOID.BASIC_CONSTRAINTS: # set by default in profile, so ignore it
continue

# Add any extension from the profile currently not changeable in the web interface
extensions[oid] = ext # pragma: no cover # all extensions should be handled above!
Expand Down
20 changes: 6 additions & 14 deletions ca/django_ca/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def _get_extensions(self, extensions: typehints.ExtensionDict) -> None:
else:
extensions.setdefault(oid, ext)

def create_cert( # noqa: PLR0913 # pylint: disable=too-many-locals
def create_cert( # noqa: PLR0913
self,
ca: "CertificateAuthority",
csr: x509.CertificateSigningRequest,
Expand Down Expand Up @@ -322,20 +322,12 @@ def create_cert( # noqa: PLR0913 # pylint: disable=too-many-locals
),
)

# Add he BasicConstraints extension
basic_constraints = typing.cast(
x509.Extension[x509.BasicConstraints],
cert_extensions.setdefault(
ExtensionOID.BASIC_CONSTRAINTS,
x509.Extension(
oid=ExtensionOID.BASIC_CONSTRAINTS,
critical=True,
value=x509.BasicConstraints(ca=False, path_length=None),
),
),
# Set the BasicConstraints extension (we do not allow the user to pass this extension)
cert_extensions[ExtensionOID.BASIC_CONSTRAINTS] = x509.Extension(
oid=ExtensionOID.BASIC_CONSTRAINTS,
critical=True,
value=x509.BasicConstraints(ca=False, path_length=None),
)
if basic_constraints.value.ca is True:
raise ValueError("BasicConstraints must have ca=False")

serial = x509.random_serial_number()
signer_serial = ca.pub.loaded.serial_number
Expand Down
38 changes: 38 additions & 0 deletions ca/django_ca/tests/admin/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 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/>.

"""Extra fixtures for tests for the admin interface."""

from typing import Iterator

from django.test import Client
from django.urls import reverse

import pytest
from _pytest.fixtures import SubRequest

from django_ca.tests.base.typehints import User


@pytest.fixture(params=["name_to_rfc4514"])
def extra_view_url(request: "SubRequest") -> Iterator[str]:
"""Parametrized fixture providing reversed extra view URLs."""
yield reverse(f"admin:django_ca_certificate_{request.param}")


@pytest.fixture()
def staff_client(user: "User", user_client: Client) -> Iterator[Client]:
"""Client with a staff user with no extra permissions."""
user.is_staff = True
user.save()
yield user_client
46 changes: 46 additions & 0 deletions ca/django_ca/tests/admin/test_extra_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,56 @@
from django.test import Client, TestCase
from django.urls import reverse

import pytest

from django_ca import constants
from django_ca.models import CertificateAuthority
from django_ca.tests.admin.base import CertificateModelAdminTestCaseMixin
from django_ca.tests.base.constants import CERT_DATA
from django_ca.typehints import JSON


@pytest.mark.parametrize(
"data,expected",
(
([], ""),
([{"oid": NameOID.COMMON_NAME.dotted_string, "value": "example.com"}], "CN=example.com"),
(
[
{"oid": NameOID.COUNTRY_NAME.dotted_string, "value": "AT"},
{"oid": NameOID.ORGANIZATION_NAME.dotted_string, "value": "MyOrg"},
{"oid": NameOID.COMMON_NAME.dotted_string, "value": "example.com"},
],
"C=AT,O=MyOrg,CN=example.com",
),
),
)
def test_name_to_rfc4514_view(admin_client: Client, data: JSON, expected: str) -> None:
"""Test admin API for converting names to RFC 4514 strings."""
url = reverse("admin:django_ca_certificate_name_to_rfc4514")
response = admin_client.post(url, data=json.dumps(data), content_type="application/json")
assert response.status_code == HTTPStatus.OK
assert response.json() == {"name": expected}


def test_unauthenticated(client: Client, extra_view_url: str) -> None:
"""Test that extra views cannot be accessed by an unauthenticated user."""
response = client.get(extra_view_url)
assert response.status_code == HTTPStatus.FOUND
assert response["Location"] == f"/admin/login/?next={extra_view_url}"


def test_no_permissions(user_client: Client, extra_view_url: str) -> None:
"""Test that extra views cannot be accessed by a user that is not a staff user."""
response = user_client.get(extra_view_url)
assert response.status_code == HTTPStatus.FOUND
assert response["Location"] == f"/admin/login/?next={extra_view_url}"


def test_staff_user_with_no_permissions(staff_client: Client, extra_view_url: str) -> None:
"""Test that extra views cannot be accessed by a staff user that does not have any permissions."""
response = staff_client.get(extra_view_url)
assert response.status_code == HTTPStatus.FORBIDDEN


class CSRDetailTestCase(CertificateModelAdminTestCaseMixin, TestCase):
Expand Down
20 changes: 11 additions & 9 deletions ca/django_ca/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
from cryptography import x509
from cryptography.x509.oid import CertificatePoliciesOID, ExtensionOID, NameOID

from django.test import Client

import pytest
from _pytest.config import Config as PytestConfig
from _pytest.config.argparsing import Parser
Expand Down Expand Up @@ -152,7 +154,10 @@ def any_cert(request: "SubRequest") -> Iterator[Certificate]:

@pytest.fixture()
def user(
db: None, # pylint: disable=unused-argument # required for database access
# PYLINT NOTE: usefixtures() does not (yet?) work with fixtures as of pytest==7.4.3
# https://docs.pytest.org/en/7.4.x/how-to/fixtures.html
# https://github.com/pytest-dev/pytest/issues/3664
db: None, # pylint: disable=unused-argument
django_user_model: Type["User"],
) -> "User":
"""Fixture for a basic Django user with no extra permissions."""
Expand All @@ -165,14 +170,11 @@ def user(
return user


# Not yet used:
# @pytest.fixture()
# def user_client(db: None, user: "User") -> Client:
# """A Django test client logged in as a normal user."""
#
# client = Client()
# client.force_login(user)
# return client
@pytest.fixture()
def user_client(user: "User", client: Client) -> Iterator[Client]:
"""A Django test client logged in as a normal user."""
client.force_login(user)
yield client


@pytest.fixture()
Expand Down
27 changes: 27 additions & 0 deletions ca/django_ca/tests/pydantic/test_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@
from django_ca import constants
from django_ca.pydantic.extension_attributes import (
AccessDescriptionModel,
BasicConstraintsValueModel,
DistributionPointModel,
IssuingDistributionPointValueModel,
SignedCertificateTimestampModel,
UnrecognizedExtensionValueModel,
)
from django_ca.pydantic.extensions import (
EXTENSION_MODEL_OIDS,
Expand Down Expand Up @@ -1482,6 +1484,31 @@ def test_unrecognized_extension(
assert_extension_model(UnrecognizedExtensionModel, parameters, extension_type, critical)


def test_certificate_extension_list_type_adapter() -> None:
"""Test type adapter for lists of extensions."""
assert CertificateExtensionsList.validate_python([]) == []
basic_constraints_model = BasicConstraintsModel(value=BasicConstraintsValueModel(ca=True, path_length=0))
input_list = [
basic_constraints_model.cryptography,
basic_constraints_model,
basic_constraints_model.model_dump(mode="json"),
x509.Extension(
oid=x509.ObjectIdentifier("1.2.3"),
critical=True,
value=x509.UnrecognizedExtension(oid=x509.ObjectIdentifier("1.2.3"), value=b"\x90"),
),
]
expected_list = [
basic_constraints_model,
basic_constraints_model,
basic_constraints_model,
UnrecognizedExtensionModel(
critical=True, value=UnrecognizedExtensionValueModel(oid="1.2.3", value=b"kA==")
),
]
assert CertificateExtensionsList.validate_python(input_list) == expected_list


def test_extension_model_oids() -> None:
"""Test EXTENSION_MODEL_OIDS constant for correctness and completeness."""
actual_oids = sorted(EXTENSION_MODEL_OIDS.values(), key=lambda oid: oid.dotted_string)
Expand Down
7 changes: 7 additions & 0 deletions ca/django_ca/tests/test_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,13 @@ def test_eq() -> None:
assert prof != -1


def test_eq_default_proxy() -> None:
"""Test equality for the default proxy."""
assert profile == profile # noqa: PLR0124 # what we're testing
assert profile == profiles[ca_settings.CA_DEFAULT_PROFILE] # proxy is equal to default profile
assert profile != ["not-equal"] # we are not equal to arbitrary stuff


def test_init_django_ca_values(name: x509.Name) -> None:
"""Test passing serialized extensions leads to equal profiles."""
prof1 = Profile("test", subject=name, extensions={"ocsp_no_check": {}})
Expand Down

0 comments on commit e9a7649

Please sign in to comment.