Files
training-software/certificates/views.py
Paperclip CTO 4f7232db56
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
feat(TRA-243): public certificate verification endpoint with hash integrity check
- CertificateVerifyView: GET /certificates/verify/{serial}/ — AllowAny
- Re-computes SHA-256 from stored PDF and compares to archived hash
- Returns {valid, serial_number, course_title, issued_at, hash} — no PII
- Returns 404 for non-completed or non-existent certificates
- 7 integration tests: hash match, tampered hash, missing file, PII exclusion, 404 cases

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:24:05 +02:00

59 lines
1.9 KiB
Python

from django.shortcuts import get_object_or_404
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Certificate, CertificateStatus
from .renderer import compute_pdf_hash
from .serializers import CertificateSerializer
class MyCertificatesView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
certs = Certificate.objects.filter(user=request.user).select_related("course")
return Response(CertificateSerializer(certs, many=True).data)
class CertificateDetailView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, cert_id):
cert = get_object_or_404(Certificate, pk=cert_id, user=request.user)
return Response(CertificateSerializer(cert).data)
class CertificateVerifyView(APIView):
"""
Public endpoint for QR/hash-based certificate verification.
Returns minimal info only (no PII beyond course name and issue date).
Also validates hash integrity against the stored PDF.
"""
permission_classes = [AllowAny]
def get(self, request, serial_number):
cert = get_object_or_404(
Certificate,
serial_number=serial_number,
status=CertificateStatus.COMPLETED,
)
# Verify hash integrity (re-compute from stored file)
hash_valid = False
if cert.pdf_path and cert.pdf_hash:
try:
actual_hash = compute_pdf_hash(cert.pdf_path)
hash_valid = actual_hash == cert.pdf_hash
except (OSError, FileNotFoundError):
hash_valid = False
return Response({
"valid": hash_valid,
"serial_number": cert.serial_number,
"course_title": cert.course.title,
"issued_at": cert.issued_at,
"hash": cert.pdf_hash,
})