From 3d74bdb5902cfa57e1897a99301dadb7bfcf2daf Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Thu, 31 Mar 2016 19:06:31 -0400 Subject: [PATCH] Add document embedded signature signing support. --- mayan/apps/document_signatures/apps.py | 4 + mayan/apps/document_signatures/forms.py | 4 +- mayan/apps/document_signatures/links.py | 10 +- mayan/apps/document_signatures/managers.py | 23 ++++ mayan/apps/document_signatures/permissions.py | 4 + mayan/apps/document_signatures/urls.py | 6 + mayan/apps/document_signatures/views.py | 112 +++++++++++++++++- 7 files changed, 157 insertions(+), 6 deletions(-) diff --git a/mayan/apps/document_signatures/apps.py b/mayan/apps/document_signatures/apps.py index 516838cc33..eecfad9102 100644 --- a/mayan/apps/document_signatures/apps.py +++ b/mayan/apps/document_signatures/apps.py @@ -25,6 +25,7 @@ from .links import ( link_document_signature_list, link_document_version_signature_delete, link_document_version_signature_detached_create, + link_document_version_signature_embedded_create, link_document_version_signature_details, link_document_version_signature_download, link_document_version_signature_list, @@ -32,6 +33,7 @@ from .links import ( ) from .permissions import ( permission_document_version_sign_detached, + permission_document_version_sign_embedded, permission_document_version_signature_delete, permission_document_version_signature_download, permission_document_version_signature_upload, @@ -77,6 +79,7 @@ class DocumentSignaturesApp(MayanAppConfig): ModelPermission.register( model=Document, permissions=( permission_document_version_sign_detached, + permission_document_version_sign_embedded, permission_document_version_signature_delete, permission_document_version_signature_download, permission_document_version_signature_view, @@ -132,6 +135,7 @@ class DocumentSignaturesApp(MayanAppConfig): links=( link_document_version_signature_list, link_document_version_signature_detached_create, + link_document_version_signature_embedded_create ), sources=(DocumentVersion,) ) menu_object.bind_links( diff --git a/mayan/apps/document_signatures/forms.py b/mayan/apps/document_signatures/forms.py index 0ee13c12c2..15c8f46663 100644 --- a/mayan/apps/document_signatures/forms.py +++ b/mayan/apps/document_signatures/forms.py @@ -18,7 +18,7 @@ from .models import SignatureBaseModel logger = logging.getLogger(__name__) -class DocumentVersionDetachedSignatureCreateForm(forms.Form): +class DocumentVersionSignatureCreateForm(forms.Form): key = forms.ModelChoiceField( label=_('Key'), queryset=Key.objects.none() ) @@ -32,7 +32,7 @@ class DocumentVersionDetachedSignatureCreateForm(forms.Form): user = kwargs.pop('user', None) logger.debug('user: %s', user) super( - DocumentVersionDetachedSignatureCreateForm, self + DocumentVersionSignatureCreateForm, self ).__init__(*args, **kwargs) queryset = Key.objects.private_keys() diff --git a/mayan/apps/document_signatures/links.py b/mayan/apps/document_signatures/links.py index 5c5d3acc28..72ee5d4f5f 100644 --- a/mayan/apps/document_signatures/links.py +++ b/mayan/apps/document_signatures/links.py @@ -6,6 +6,8 @@ from django.utils.translation import ugettext_lazy as _ from navigation import Link from .permissions import ( + permission_document_version_sign_detached, + permission_document_version_sign_embedded, permission_document_version_signature_delete, permission_document_version_signature_download, permission_document_version_signature_upload, @@ -69,7 +71,13 @@ link_document_version_signature_upload = Link( ) link_document_version_signature_detached_create = Link( args='resolved_object.pk', - permissions=(permission_document_version_signature_upload,), + permissions=(permission_document_version_sign_detached,), permissions_related='document', text=_('Sign detached'), view='signatures:document_version_signature_detached_create', ) +link_document_version_signature_embedded_create = Link( + args='resolved_object.pk', + permissions=(permission_document_version_sign_embedded,), + permissions_related='document', text=_('Sign embedded'), + view='signatures:document_version_signature_embedded_create', +) diff --git a/mayan/apps/document_signatures/managers.py b/mayan/apps/document_signatures/managers.py index 9c3b90de19..7252d9211c 100644 --- a/mayan/apps/document_signatures/managers.py +++ b/mayan/apps/document_signatures/managers.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals import logging +import os +import tempfile from django.db import models @@ -30,3 +32,24 @@ class EmbeddedSignatureManager(models.Manager): return DocumentVersion.objects.exclude( pk__in=self.values('document_version') ) + + def sign_document_version(self, document_version, key, passphrase=None, user=None): + temporary_file_object, temporary_filename = tempfile.mkstemp() + + try: + with document_version.open() as file_object: + signature_result = key.sign_file( + binary=True, file_object=file_object, + output=temporary_filename, passphrase=passphrase + ) + except Exception: + raise + else: + with open(temporary_filename) as file_object: + new_version = document_version.document.new_version( + file_object=file_object, _user=user + ) + finally: + os.unlink(temporary_filename) + + return new_version diff --git a/mayan/apps/document_signatures/permissions.py b/mayan/apps/document_signatures/permissions.py index 15f5cacc7c..c9bdbe684c 100644 --- a/mayan/apps/document_signatures/permissions.py +++ b/mayan/apps/document_signatures/permissions.py @@ -12,6 +12,10 @@ permission_document_version_sign_detached = namespace.add_permission( name='document_version_sign_detached', label=_('Sign documents with detached signatures') ) +permission_document_version_sign_embedded = namespace.add_permission( + name='document_version_sign_embedded', + label=_('Sign documents with embedded signatures') +) permission_document_version_signature_delete = namespace.add_permission( name='document_version_signature_delete', label=_('Delete detached signatures') diff --git a/mayan/apps/document_signatures/urls.py b/mayan/apps/document_signatures/urls.py index 3d6a584e82..1c269c3d65 100644 --- a/mayan/apps/document_signatures/urls.py +++ b/mayan/apps/document_signatures/urls.py @@ -4,6 +4,7 @@ from django.conf.urls import patterns, url from .views import ( AllDocumentSignatureVerifyView, DocumentVersionDetachedSignatureCreateView, + DocumentVersionEmbeddedSignatureCreateView, DocumentVersionSignatureDeleteView, DocumentVersionSignatureDetailView, DocumentVersionSignatureDownloadView, DocumentVersionSignatureListView, DocumentVersionSignatureUploadView @@ -36,6 +37,11 @@ urlpatterns = patterns( DocumentVersionDetachedSignatureCreateView.as_view(), name='document_version_signature_detached_create' ), + url( + r'^documents/version/(?P\d+)/signature/embedded/create/$', + DocumentVersionEmbeddedSignatureCreateView.as_view(), + name='document_version_signature_embedded_create' + ), url( r'^signature/(?P\d+)/delete/$', DocumentVersionSignatureDeleteView.as_view(), diff --git a/mayan/apps/document_signatures/views.py b/mayan/apps/document_signatures/views.py index 919fa5dbd1..8e483062f6 100644 --- a/mayan/apps/document_signatures/views.py +++ b/mayan/apps/document_signatures/views.py @@ -22,12 +22,13 @@ from documents.models import DocumentVersion from permissions import Permission from .forms import ( - DocumentVersionDetachedSignatureCreateForm, + DocumentVersionSignatureCreateForm, DocumentVersionSignatureDetailForm ) from .models import DetachedSignature, SignatureBaseModel from .permissions import ( permission_document_version_sign_detached, + permission_document_version_sign_embedded, permission_document_version_signature_delete, permission_document_version_signature_download, permission_document_version_signature_upload, @@ -40,7 +41,7 @@ logger = logging.getLogger(__name__) class DocumentVersionDetachedSignatureCreateView(FormView): - form_class = DocumentVersionDetachedSignatureCreateForm + form_class = DocumentVersionSignatureCreateForm def form_valid(self, form): key = form.cleaned_data['key'] @@ -125,7 +126,7 @@ class DocumentVersionDetachedSignatureCreateView(FormView): 'document_version': self.get_document_version(), 'navigation_object_list': ('document', 'document_version'), 'title': _( - 'Sign document version "%s" with a detached signature?' + 'Sign document version "%s" with a detached signature' ) % self.get_document_version(), } @@ -145,6 +146,111 @@ class DocumentVersionDetachedSignatureCreateView(FormView): ) +class DocumentVersionEmbeddedSignatureCreateView(FormView): + form_class = DocumentVersionSignatureCreateForm + + def form_valid(self, form): + key = form.cleaned_data['key'] + passphrase = form.cleaned_data['passphrase'] or None + + try: + Permission.check_permissions( + self.request.user, (permission_key_sign,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_key_sign, self.request.user, key + ) + + try: + with self.get_document_version().open() as file_object: + signature_result = key.sign_file( + binary=True, file_object=file_object, passphrase=passphrase + ) + except NeedPassphrase: + messages.error( + self.request, _('Passphrase is needed to unlock this key.') + ) + return HttpResponseRedirect( + reverse( + 'signatures:document_version_signature_embedded_create', + args=(self.get_document_version().pk,) + ) + ) + except PassphraseError: + messages.error( + self.request, _('Passphrase is incorrect.') + ) + return HttpResponseRedirect( + reverse( + 'signatures:document_version_signature_embedded_create', + args=(self.get_document_version().pk,) + ) + ) + else: + temporary_file_object = tempfile.TemporaryFile() + temporary_file_object.write(signature_result.data) + temporary_file_object.seek(0) + + new_version = self.get_document_version().document.new_version( + file_object=temporary_file_object, _user=self.request.user + ) + + temporary_file_object.close() + + messages.success( + self.request, _('Document version signed successfully.') + ) + + return HttpResponseRedirect( + reverse( + 'signatures:document_version_signature_list', + args=(new_version.pk,) + ) + ) + + return super( + DocumentVersionEmbeddedSignatureCreateView, self + ).form_valid(form) + + def dispatch(self, request, *args, **kwargs): + try: + Permission.check_permissions( + request.user, (permission_document_version_sign_embedded,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_document_version_sign_embedded, request.user, + self.get_document_version().document + ) + + return super( + DocumentVersionEmbeddedSignatureCreateView, self + ).dispatch(request, *args, **kwargs) + + def get_document_version(self): + return get_object_or_404(DocumentVersion, pk=self.kwargs['pk']) + + def get_extra_context(self): + return { + 'document': self.get_document_version().document, + 'document_version': self.get_document_version(), + 'navigation_object_list': ('document', 'document_version'), + 'title': _( + 'Sign document version "%s" with a embedded signature' + ) % self.get_document_version(), + } + + def get_form_kwargs(self): + result = super( + DocumentVersionEmbeddedSignatureCreateView, self + ).get_form_kwargs() + + result.update({'user': self.request.user}) + + return result + + class DocumentVersionSignatureDeleteView(SingleObjectDeleteView): model = DetachedSignature object_permission = permission_document_version_signature_delete