import base64 from io import BytesIO from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import serialization from cryptography.fernet import Fernet, InvalidToken from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import PermissionDenied from django.core.files.base import ContentFile from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import get_object_or_404, reverse from django.template.loader import render_to_string from django.utils import timezone from django.utils.text import slugify from django.views.generic import FormView, TemplateView, View from aemo.models import Famille from aemo.utils import is_ajax from .models import Archive from .pdf import ArchivePdf from .forms import ArchiveFilterForm, ArchiveKeyUploadForm class ArchiveCreateView(View): def post(self, request, *args, **kwargs): unite = 'aemo' temp = BytesIO() famille = get_object_or_404(Famille, pk=kwargs['pk'], archived_at__isnull=True) intervenants_list = '/'.join([f"{interv.nom} {interv.prenom[0].upper()}." for interv in famille.suivi.intervenants.all()]) ope_list = famille.suivi.ope_referents motif_fin = famille.suivi.get_motif_fin_suivi_display() pdf = ArchivePdf(temp, famille) if not famille.can_be_archived(self.request.user): raise PermissionDenied("Vous n'avez pas les droits nécessaires pour accéder à cette page.") famille.archived_at = timezone.now() pdf.produce() filename = f"{unite}/{pdf.get_filename()}" temp.seek(0) pdf = temp.read() # Create a symmetric Fernet key to encrypt the PDF, and encrypt that key with asymmetric RSA key. key = Fernet.generate_key() fernet = Fernet(key) pdf_crypted = fernet.encrypt(pdf) with open(settings.CRNE_RSA_PUBLIC_KEY, "rb") as key_file: public_key = serialization.load_ssh_public_key(key_file.read()) padd = padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None) fernet_crypted = public_key.encrypt(key, padding=padd) nom_famille = famille.nom arch = Archive.objects.create( nom=nom_famille, unite=unite, intervenant=intervenants_list, ope=", ".join([ope.nom_prenom for ope in ope_list]), motif_fin=motif_fin, date_debut=famille.suivi.date_debut_suivi, date_fin=famille.suivi.date_fin_suivi, key=base64.b64encode(fernet_crypted).decode(), pdf=None ) arch.pdf.save(filename, ContentFile(pdf_crypted)) famille.archived_at = timezone.now() famille.save() famille.anonymiser() if is_ajax(request): return JsonResponse({'id': famille.pk}, safe=True) messages.success(request, f"La famille «{nom_famille}» a bien été archivée.") return HttpResponseRedirect(reverse("suivis-termines")) class ArchiveListView(TemplateView): template_name = 'archive/list.html' model = Archive def dispatch(self, request, *args, **kwargs): self.unite = self.kwargs['unite'] self.filter_form = ArchiveFilterForm(data=request.GET or None) return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): if is_ajax(request) and self.unite == 'aemo': if self.filter_form.is_bound and self.filter_form.is_valid(): archives = self.filter_form.filter(Archive.objects.filter(unite=self.unite)) else: archives = Archive.objects.none() response = render_to_string( template_name='archive/list_partial.html', context={ 'archives': archives, 'can_download': request.user.has_perm('aemo.can_archive'), } ) return JsonResponse(response, safe=False) return super().get(request, *args, **kwargs) def get_context_data(self, *args, **kwargs): return { **super().get_context_data(*args, **kwargs), 'unite': self.unite, 'form': self.filter_form, 'archives': Archive.objects.filter(unite=self.unite), 'can_download': self.request.user.has_perm('aemo.can_archive'), } class ArchiveDecryptView(PermissionRequiredMixin, FormView): form_class = ArchiveKeyUploadForm template_name = 'archive/key_upload.html' permission_required = 'aemo.can_archive' def form_valid(self, form): arch = get_object_or_404(Archive, pk=self.kwargs['pk']) try: with open(arch.pdf.path, "rb") as fh: pdf_crypted = fh.read() except OSError as err: messages.error(self.request, f"Erreur lors de la lecture du document ({str(err)})") return HttpResponseRedirect(reverse('archive-list', args=[arch.unite])) try: private_key_file = self.request.FILES['file'].read() private_key = serialization.load_pem_private_key(private_key_file, password=None) padd = padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None) sim_key = private_key.decrypt(base64.b64decode(arch.key), padding=padd) fernet = Fernet(sim_key) pdf_content = fernet.decrypt(pdf_crypted) except ValueError as err: messages.error(self.request, f"Erreur lors de la lecture de la clé ({str(err)})") return HttpResponseRedirect(reverse('archive-list', args=[arch.unite])) except InvalidToken: messages.error(self.request, "Erreur lors du déchiffrement") return HttpResponseRedirect(reverse('archive-list', args=[arch.unite])) filename = f"{slugify(arch.nom)}.pdf" response = HttpResponse(pdf_content, content_type='application/pdf') response['Content-Disposition'] = "attachment; filename=%s" % filename return response