crypto: add ?download flag
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> #861
This commit is contained in:
parent
a6c6f22221
commit
24f2932777
|
@ -3,7 +3,9 @@ import django_filters
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||||
from cryptography.x509 import load_pem_x509_certificate
|
from cryptography.x509 import load_pem_x509_certificate
|
||||||
|
from django.http.response import HttpResponse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from drf_yasg import openapi
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import (
|
from rest_framework.fields import (
|
||||||
|
@ -145,7 +147,16 @@ class CertificateKeyPairViewSet(ModelViewSet):
|
||||||
serializer = self.get_serializer(instance)
|
serializer = self.get_serializer(instance)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@swagger_auto_schema(responses={200: CertificateDataSerializer(many=False)})
|
@swagger_auto_schema(
|
||||||
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
name="download",
|
||||||
|
in_=openapi.IN_QUERY,
|
||||||
|
type=openapi.TYPE_BOOLEAN,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
responses={200: CertificateDataSerializer(many=False)},
|
||||||
|
)
|
||||||
@action(detail=True, pagination_class=None, filter_backends=[])
|
@action(detail=True, pagination_class=None, filter_backends=[])
|
||||||
# pylint: disable=invalid-name, unused-argument
|
# pylint: disable=invalid-name, unused-argument
|
||||||
def view_certificate(self, request: Request, pk: str) -> Response:
|
def view_certificate(self, request: Request, pk: str) -> Response:
|
||||||
|
@ -156,11 +167,29 @@ class CertificateKeyPairViewSet(ModelViewSet):
|
||||||
secret=certificate,
|
secret=certificate,
|
||||||
type="certificate",
|
type="certificate",
|
||||||
).from_http(request)
|
).from_http(request)
|
||||||
|
if "download" in request._request.GET:
|
||||||
|
# Mime type from https://pki-tutorial.readthedocs.io/en/latest/mime.html
|
||||||
|
response = HttpResponse(
|
||||||
|
certificate.certificate_data, content_type="application/x-pem-file"
|
||||||
|
)
|
||||||
|
response[
|
||||||
|
"Content-Disposition"
|
||||||
|
] = f'attachment; filename="{certificate.name}_certificate.pem"'
|
||||||
|
return response
|
||||||
return Response(
|
return Response(
|
||||||
CertificateDataSerializer({"data": certificate.certificate_data}).data
|
CertificateDataSerializer({"data": certificate.certificate_data}).data
|
||||||
)
|
)
|
||||||
|
|
||||||
@swagger_auto_schema(responses={200: CertificateDataSerializer(many=False)})
|
@swagger_auto_schema(
|
||||||
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
name="download",
|
||||||
|
in_=openapi.IN_QUERY,
|
||||||
|
type=openapi.TYPE_BOOLEAN,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
responses={200: CertificateDataSerializer(many=False)},
|
||||||
|
)
|
||||||
@action(detail=True, pagination_class=None, filter_backends=[])
|
@action(detail=True, pagination_class=None, filter_backends=[])
|
||||||
# pylint: disable=invalid-name, unused-argument
|
# pylint: disable=invalid-name, unused-argument
|
||||||
def view_private_key(self, request: Request, pk: str) -> Response:
|
def view_private_key(self, request: Request, pk: str) -> Response:
|
||||||
|
@ -171,4 +200,13 @@ class CertificateKeyPairViewSet(ModelViewSet):
|
||||||
secret=certificate,
|
secret=certificate,
|
||||||
type="private_key",
|
type="private_key",
|
||||||
).from_http(request)
|
).from_http(request)
|
||||||
|
if "download" in request._request.GET:
|
||||||
|
# Mime type from https://pki-tutorial.readthedocs.io/en/latest/mime.html
|
||||||
|
response = HttpResponse(
|
||||||
|
certificate.key_data, content_type="application/x-pem-file"
|
||||||
|
)
|
||||||
|
response[
|
||||||
|
"Content-Disposition"
|
||||||
|
] = f'attachment; filename="{certificate.name}_private_key.pem"'
|
||||||
|
return response
|
||||||
return Response(CertificateDataSerializer({"data": certificate.key_data}).data)
|
return Response(CertificateDataSerializer({"data": certificate.key_data}).data)
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from authentik.core.models import User
|
||||||
from authentik.crypto.api import CertificateKeyPairSerializer
|
from authentik.crypto.api import CertificateKeyPairSerializer
|
||||||
from authentik.crypto.builder import CertificateBuilder
|
from authentik.crypto.builder import CertificateBuilder
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
|
@ -47,3 +49,45 @@ class TestCrypto(TestCase):
|
||||||
now = datetime.datetime.today()
|
now = datetime.datetime.today()
|
||||||
self.assertEqual(instance.name, "test-cert")
|
self.assertEqual(instance.name, "test-cert")
|
||||||
self.assertEqual((instance.certificate.not_valid_after - now).days, 2)
|
self.assertEqual((instance.certificate.not_valid_after - now).days, 2)
|
||||||
|
|
||||||
|
def test_certificate_download(self):
|
||||||
|
"""Test certificate export (download)"""
|
||||||
|
self.client.force_login(User.objects.get(username="akadmin"))
|
||||||
|
keypair = CertificateKeyPair.objects.first()
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"authentik_api:certificatekeypair-view-certificate",
|
||||||
|
kwargs={"pk": keypair.pk},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(200, response.status_code)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"authentik_api:certificatekeypair-view-certificate",
|
||||||
|
kwargs={"pk": keypair.pk},
|
||||||
|
)
|
||||||
|
+ "?download",
|
||||||
|
)
|
||||||
|
self.assertEqual(200, response.status_code)
|
||||||
|
self.assertIn("Content-Disposition", response)
|
||||||
|
|
||||||
|
def test_private_key_download(self):
|
||||||
|
"""Test private_key export (download)"""
|
||||||
|
self.client.force_login(User.objects.get(username="akadmin"))
|
||||||
|
keypair = CertificateKeyPair.objects.first()
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"authentik_api:certificatekeypair-view-private-key",
|
||||||
|
kwargs={"pk": keypair.pk},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(200, response.status_code)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"authentik_api:certificatekeypair-view-private-key",
|
||||||
|
kwargs={"pk": keypair.pk},
|
||||||
|
)
|
||||||
|
+ "?download",
|
||||||
|
)
|
||||||
|
self.assertEqual(200, response.status_code)
|
||||||
|
self.assertIn("Content-Disposition", response)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from defusedxml.ElementTree import fromstring
|
||||||
from django.http.response import HttpResponse
|
from django.http.response import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from drf_yasg import openapi
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import CharField, FileField, ReadOnlyField
|
from rest_framework.fields import CharField, FileField, ReadOnlyField
|
||||||
|
@ -83,7 +84,14 @@ class SAMLProviderViewSet(ModelViewSet):
|
||||||
responses={
|
responses={
|
||||||
200: SAMLMetadataSerializer(many=False),
|
200: SAMLMetadataSerializer(many=False),
|
||||||
404: "Provider has no application assigned",
|
404: "Provider has no application assigned",
|
||||||
}
|
},
|
||||||
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
name="download",
|
||||||
|
in_=openapi.IN_QUERY,
|
||||||
|
type=openapi.TYPE_BOOLEAN,
|
||||||
|
)
|
||||||
|
],
|
||||||
)
|
)
|
||||||
@action(methods=["GET"], detail=True, permission_classes=[AllowAny])
|
@action(methods=["GET"], detail=True, permission_classes=[AllowAny])
|
||||||
# pylint: disable=invalid-name, unused-argument
|
# pylint: disable=invalid-name, unused-argument
|
||||||
|
|
15
swagger.yaml
15
swagger.yaml
|
@ -2527,7 +2527,10 @@ paths:
|
||||||
get:
|
get:
|
||||||
operationId: crypto_certificatekeypairs_view_certificate
|
operationId: crypto_certificatekeypairs_view_certificate
|
||||||
description: Return certificate-key pairs certificate and log access
|
description: Return certificate-key pairs certificate and log access
|
||||||
parameters: []
|
parameters:
|
||||||
|
- name: download
|
||||||
|
in: query
|
||||||
|
type: boolean
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: ''
|
description: ''
|
||||||
|
@ -2555,7 +2558,10 @@ paths:
|
||||||
get:
|
get:
|
||||||
operationId: crypto_certificatekeypairs_view_private_key
|
operationId: crypto_certificatekeypairs_view_private_key
|
||||||
description: Return certificate-key pairs private key and log access
|
description: Return certificate-key pairs private key and log access
|
||||||
parameters: []
|
parameters:
|
||||||
|
- name: download
|
||||||
|
in: query
|
||||||
|
type: boolean
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: ''
|
description: ''
|
||||||
|
@ -9696,7 +9702,10 @@ paths:
|
||||||
get:
|
get:
|
||||||
operationId: providers_saml_metadata
|
operationId: providers_saml_metadata
|
||||||
description: Return metadata as XML string
|
description: Return metadata as XML string
|
||||||
parameters: []
|
parameters:
|
||||||
|
- name: download
|
||||||
|
in: query
|
||||||
|
type: boolean
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: ''
|
description: ''
|
||||||
|
|
Reference in New Issue