mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
crypto: separate permissions for certificate and private keydownload (#18588)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@@ -27,6 +27,7 @@ from rest_framework.fields import (
|
||||
SerializerMethodField,
|
||||
)
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.validators import UniqueValidator
|
||||
@@ -42,7 +43,7 @@ from authentik.crypto.builder import CertificateBuilder, PrivateKeyAlg
|
||||
from authentik.crypto.models import CertificateKeyPair, KeyType
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.rbac.decorators import permission_required
|
||||
from authentik.rbac.filters import ObjectFilter, SecretKeyFilter
|
||||
from authentik.rbac.filters import SecretKeyFilter
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@@ -292,6 +293,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
|
||||
serializer = self.get_serializer(instance)
|
||||
return Response(serializer.data)
|
||||
|
||||
@permission_required("view_certificatekeypair_certificate")
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
@@ -302,7 +304,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
|
||||
],
|
||||
responses={200: CertificateDataSerializer(many=False)},
|
||||
)
|
||||
@action(detail=True, pagination_class=None, filter_backends=[ObjectFilter])
|
||||
@action(detail=True, pagination_class=None, permission_classes=[IsAuthenticated])
|
||||
def view_certificate(self, request: Request, pk: str) -> Response:
|
||||
"""Return certificate-key pairs certificate and log access"""
|
||||
certificate: CertificateKeyPair = self.get_object()
|
||||
@@ -323,6 +325,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
|
||||
return response
|
||||
return Response(CertificateDataSerializer({"data": certificate.certificate_data}).data)
|
||||
|
||||
@permission_required("view_certificatekeypair_key")
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
@@ -333,7 +336,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
|
||||
],
|
||||
responses={200: CertificateDataSerializer(many=False)},
|
||||
)
|
||||
@action(detail=True, pagination_class=None, filter_backends=[ObjectFilter])
|
||||
@action(detail=True, pagination_class=None, permission_classes=[IsAuthenticated])
|
||||
def view_private_key(self, request: Request, pk: str) -> Response:
|
||||
"""Return certificate-key pairs private key and log access"""
|
||||
certificate: CertificateKeyPair = self.get_object()
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-20 14:50
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_crypto", "0004_alter_certificatekeypair_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="certificatekeypair",
|
||||
options={
|
||||
"permissions": [
|
||||
(
|
||||
"view_certificatekeypair_certificate",
|
||||
"View Certificate-Key pair's certificate",
|
||||
),
|
||||
("view_certificatekeypair_key", "View Certificate-Key pair's private key"),
|
||||
],
|
||||
"verbose_name": "Certificate-Key Pair",
|
||||
"verbose_name_plural": "Certificate-Key Pairs",
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -140,3 +140,7 @@ class CertificateKeyPair(SerializerModel, ManagedModel, CreatedUpdatedModel):
|
||||
class Meta:
|
||||
verbose_name = _("Certificate-Key Pair")
|
||||
verbose_name_plural = _("Certificate-Key Pairs")
|
||||
permissions = [
|
||||
("view_certificatekeypair_certificate", _("View Certificate-Key pair's certificate")),
|
||||
("view_certificatekeypair_key", _("View Certificate-Key pair's private key")),
|
||||
]
|
||||
|
||||
+25
-13
@@ -9,10 +9,16 @@ from cryptography.x509.extensions import SubjectAlternativeName
|
||||
from cryptography.x509.general_name import DNSName
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from guardian.shortcuts import assign_perm
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.api.used_by import DeleteAction
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.core.tests.utils import (
|
||||
create_test_admin_user,
|
||||
create_test_cert,
|
||||
create_test_flow,
|
||||
create_test_user,
|
||||
)
|
||||
from authentik.crypto.api import CertificateKeyPairSerializer
|
||||
from authentik.crypto.builder import CertificateBuilder
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
@@ -144,7 +150,7 @@ class TestCrypto(APITestCase):
|
||||
),
|
||||
data={"name": cert.name},
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
body = loads(response.content.decode())
|
||||
api_cert = [x for x in body["results"] if x["name"] == cert.name][0]
|
||||
self.assertEqual(api_cert["fingerprint_sha1"], cert.fingerprint_sha1)
|
||||
@@ -162,7 +168,7 @@ class TestCrypto(APITestCase):
|
||||
),
|
||||
data={"name": cert.name, "has_key": False},
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
body = loads(response.content.decode())
|
||||
api_cert = [x for x in body["results"] if x["name"] == cert.name][0]
|
||||
self.assertEqual(api_cert["fingerprint_sha1"], cert.fingerprint_sha1)
|
||||
@@ -178,7 +184,7 @@ class TestCrypto(APITestCase):
|
||||
),
|
||||
data={"name": cert.name, "include_details": False},
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
body = loads(response.content.decode())
|
||||
api_cert = [x for x in body["results"] if x["name"] == cert.name][0]
|
||||
self.assertEqual(api_cert["fingerprint_sha1"], None)
|
||||
@@ -186,15 +192,18 @@ class TestCrypto(APITestCase):
|
||||
|
||||
def test_certificate_download(self):
|
||||
"""Test certificate export (download)"""
|
||||
self.client.force_login(create_test_admin_user())
|
||||
keypair = create_test_cert()
|
||||
user = create_test_user()
|
||||
assign_perm("view_certificatekeypair", user, keypair)
|
||||
assign_perm("view_certificatekeypair_certificate", user, keypair)
|
||||
self.client.force_login(user)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"authentik_api:certificatekeypair-view-certificate",
|
||||
kwargs={"pk": keypair.pk},
|
||||
)
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"authentik_api:certificatekeypair-view-certificate",
|
||||
@@ -202,20 +211,23 @@ class TestCrypto(APITestCase):
|
||||
),
|
||||
data={"download": True},
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Content-Disposition", response)
|
||||
|
||||
def test_private_key_download(self):
|
||||
"""Test private_key export (download)"""
|
||||
self.client.force_login(create_test_admin_user())
|
||||
keypair = create_test_cert()
|
||||
user = create_test_user()
|
||||
assign_perm("view_certificatekeypair", user, keypair)
|
||||
assign_perm("view_certificatekeypair_key", user, keypair)
|
||||
self.client.force_login(user)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"authentik_api:certificatekeypair-view-private-key",
|
||||
kwargs={"pk": keypair.pk},
|
||||
)
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
"authentik_api:certificatekeypair-view-private-key",
|
||||
@@ -223,12 +235,12 @@ class TestCrypto(APITestCase):
|
||||
),
|
||||
data={"download": True},
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Content-Disposition", response)
|
||||
|
||||
def test_certificate_download_denied(self):
|
||||
"""Test certificate export (download)"""
|
||||
self.client.logout()
|
||||
self.client.force_login(create_test_user())
|
||||
keypair = create_test_cert()
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
@@ -248,7 +260,7 @@ class TestCrypto(APITestCase):
|
||||
|
||||
def test_private_key_download_denied(self):
|
||||
"""Test private_key export (download)"""
|
||||
self.client.logout()
|
||||
self.client.force_login(create_test_user())
|
||||
keypair = create_test_cert()
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
@@ -284,7 +296,7 @@ class TestCrypto(APITestCase):
|
||||
kwargs={"pk": keypair.pk},
|
||||
)
|
||||
)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertJSONEqual(
|
||||
response.content.decode(),
|
||||
[
|
||||
|
||||
@@ -5280,6 +5280,8 @@
|
||||
"authentik_crypto.change_certificatekeypair",
|
||||
"authentik_crypto.delete_certificatekeypair",
|
||||
"authentik_crypto.view_certificatekeypair",
|
||||
"authentik_crypto.view_certificatekeypair_certificate",
|
||||
"authentik_crypto.view_certificatekeypair_key",
|
||||
"authentik_endpoints.add_connector",
|
||||
"authentik_endpoints.add_device",
|
||||
"authentik_endpoints.add_deviceaccessgroup",
|
||||
@@ -5953,7 +5955,9 @@
|
||||
"add_certificatekeypair",
|
||||
"change_certificatekeypair",
|
||||
"delete_certificatekeypair",
|
||||
"view_certificatekeypair"
|
||||
"view_certificatekeypair",
|
||||
"view_certificatekeypair_certificate",
|
||||
"view_certificatekeypair_key"
|
||||
]
|
||||
},
|
||||
"user": {
|
||||
@@ -10604,6 +10608,8 @@
|
||||
"authentik_crypto.change_certificatekeypair",
|
||||
"authentik_crypto.delete_certificatekeypair",
|
||||
"authentik_crypto.view_certificatekeypair",
|
||||
"authentik_crypto.view_certificatekeypair_certificate",
|
||||
"authentik_crypto.view_certificatekeypair_key",
|
||||
"authentik_endpoints.add_connector",
|
||||
"authentik_endpoints.add_device",
|
||||
"authentik_endpoints.add_deviceaccessgroup",
|
||||
|
||||
Reference in New Issue
Block a user