Start of document_signatures app refactor.
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
- More tests added.
|
- More tests added.
|
||||||
- Handle unicode filenames in staging folders.
|
- Handle unicode filenames in staging folders.
|
||||||
- Add staging file deletion permission.
|
- Add staging file deletion permission.
|
||||||
|
- New document_signature_view permission.
|
||||||
|
|
||||||
2.0.2 (2016-02-09)
|
2.0.2 (2016-02-09)
|
||||||
==================
|
==================
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import DocumentVersionSignature
|
#from .models import DocumentVersionSignature
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
@admin.register(DocumentVersionSignature)
|
@admin.register(DocumentVersionSignature)
|
||||||
class DocumentVersionSignatureAdmin(admin.ModelAdmin):
|
class DocumentVersionSignatureAdmin(admin.ModelAdmin):
|
||||||
def document(self, instance):
|
def document(self, instance):
|
||||||
@@ -20,3 +20,4 @@ class DocumentVersionSignatureAdmin(admin.ModelAdmin):
|
|||||||
)
|
)
|
||||||
list_display_links = ('document_version',)
|
list_display_links = ('document_version',)
|
||||||
search_fields = ('document_version__document__label',)
|
search_fields = ('document_version__document__label',)
|
||||||
|
"""
|
||||||
|
|||||||
@@ -6,16 +6,26 @@ from django.apps import apps
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from acls import ModelPermission
|
from acls import ModelPermission
|
||||||
from common import MayanAppConfig, menu_facet, menu_sidebar
|
from common import (
|
||||||
|
MayanAppConfig, menu_facet, menu_object, menu_secondary, menu_sidebar
|
||||||
|
)
|
||||||
|
from common.widgets import two_state_template
|
||||||
|
from navigation import SourceColumn
|
||||||
|
|
||||||
from .hooks import document_pre_open_hook, document_version_post_save_hook
|
|
||||||
from .links import (
|
from .links import (
|
||||||
link_document_signature_delete, link_document_signature_download,
|
link_document_version_signature_delete,
|
||||||
link_document_signature_upload, link_document_verify
|
link_document_version_signature_details,
|
||||||
|
link_document_version_signature_download,
|
||||||
|
link_document_version_signature_list,
|
||||||
|
link_document_version_signature_upload,
|
||||||
|
link_document_version_signature_verify
|
||||||
)
|
)
|
||||||
from .permissions import (
|
from .permissions import (
|
||||||
permission_document_verify, permission_signature_delete,
|
permission_document_version_signature_delete,
|
||||||
permission_signature_download, permission_signature_upload
|
permission_document_version_signature_download,
|
||||||
|
permission_document_version_signature_upload,
|
||||||
|
permission_document_version_signature_verify,
|
||||||
|
permission_document_version_signature_view,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -39,30 +49,74 @@ class DocumentSignaturesApp(MayanAppConfig):
|
|||||||
app_label='documents', model_name='DocumentVersion'
|
app_label='documents', model_name='DocumentVersion'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DetachedSignature = self.get_model('DetachedSignature')
|
||||||
|
|
||||||
|
EmbeddedSignature = self.get_model('EmbeddedSignature')
|
||||||
|
|
||||||
|
SignatureBaseModel = self.get_model('SignatureBaseModel')
|
||||||
|
|
||||||
DocumentVersion.register_post_save_hook(
|
DocumentVersion.register_post_save_hook(
|
||||||
1, document_version_post_save_hook
|
order=1, func=EmbeddedSignature.objects.check_signature
|
||||||
|
)
|
||||||
|
DocumentVersion.register_pre_open_hook(
|
||||||
|
order=1, func=EmbeddedSignature.objects.open_signed
|
||||||
)
|
)
|
||||||
DocumentVersion.register_pre_open_hook(1, document_pre_open_hook)
|
|
||||||
|
|
||||||
ModelPermission.register(
|
ModelPermission.register(
|
||||||
model=Document, permissions=(
|
model=Document, permissions=(
|
||||||
permission_document_verify, permission_signature_delete,
|
permission_document_version_signature_delete,
|
||||||
permission_signature_download, permission_signature_upload,
|
permission_document_version_signature_download,
|
||||||
|
permission_document_version_signature_verify,
|
||||||
|
permission_document_version_signature_view,
|
||||||
|
permission_document_version_signature_upload,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_facet.bind_links(
|
SourceColumn(
|
||||||
links=(link_document_verify,), sources=(Document,)
|
source=SignatureBaseModel, label=_('Date'), attribute='date'
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
source=SignatureBaseModel, label=_('Key ID'), attribute='key_id'
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
source=SignatureBaseModel, label=_('Signature ID'),
|
||||||
|
func=lambda context: context['object'].signature_id or _('None')
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
source=SignatureBaseModel, label=_('Public key ID'),
|
||||||
|
func=lambda context: context['object'].public_key_fingerprint or _('None')
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
source=SignatureBaseModel, label=_('Is embedded?'),
|
||||||
|
func=lambda context: two_state_template(
|
||||||
|
SignatureBaseModel.objects.get_subclass(
|
||||||
|
pk=context['object'].pk
|
||||||
|
).is_embedded
|
||||||
|
)
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
source=SignatureBaseModel, label=_('Is detached?'),
|
||||||
|
func=lambda context: two_state_template(
|
||||||
|
SignatureBaseModel.objects.get_subclass(
|
||||||
|
pk=context['object'].pk
|
||||||
|
).is_detached
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
menu_object.bind_links(
|
||||||
|
links=(link_document_version_signature_list,),
|
||||||
|
sources=(DocumentVersion,)
|
||||||
|
)
|
||||||
|
menu_object.bind_links(
|
||||||
|
links=(
|
||||||
|
link_document_version_signature_details,
|
||||||
|
link_document_version_signature_download,
|
||||||
|
link_document_version_signature_delete,
|
||||||
|
), sources=(SignatureBaseModel,)
|
||||||
)
|
)
|
||||||
menu_sidebar.bind_links(
|
menu_sidebar.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_document_signature_upload,
|
link_document_version_signature_upload,
|
||||||
link_document_signature_download,
|
link_document_version_signature_verify,
|
||||||
link_document_signature_delete
|
), sources=(DocumentVersion,)
|
||||||
), sources=(
|
|
||||||
'signatures:document_verify',
|
|
||||||
'signatures:document_signature_upload',
|
|
||||||
'signatures:document_signature_download',
|
|
||||||
'signatures:document_signature_delete'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,8 +3,49 @@ from __future__ import unicode_literals
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from common.forms import DetailForm
|
||||||
|
|
||||||
|
from .models import SignatureBaseModel
|
||||||
|
|
||||||
|
|
||||||
class DetachedSignatureForm(forms.Form):
|
class DetachedSignatureForm(forms.Form):
|
||||||
file = forms.FileField(
|
file = forms.FileField(
|
||||||
label=_('Signature file'),
|
label=_('Signature file'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionSignatureDetailForm(DetailForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
extra_fields = (
|
||||||
|
{'label': _('Is embedded?'), 'field': 'is_embedded'},
|
||||||
|
{'label': _('Date'), 'field': 'date'},
|
||||||
|
{'label': _('Key ID'), 'field': 'key_id'},
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs['extra_fields'] = extra_fields
|
||||||
|
super(DocumentVersionSignatureDetailForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ()
|
||||||
|
model = SignatureBaseModel
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
'label': _('User ID'),
|
||||||
|
'field': lambda x: escape(instance.user_id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Creation date'), 'field': 'creation_date',
|
||||||
|
'widget': forms.widgets.DateInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': _('Expiration date'),
|
||||||
|
'field': lambda x: instance.expiration_date or _('None'),
|
||||||
|
'widget': forms.widgets.DateInput
|
||||||
|
},
|
||||||
|
{'label': _('Fingerprint'), 'field': 'fingerprint'},
|
||||||
|
{'label': _('Length'), 'field': 'length'},
|
||||||
|
{'label': _('Algorithm'), 'field': 'algorithm'},
|
||||||
|
{'label': _('Type'), 'field': lambda x: instance.get_key_type_display()},
|
||||||
|
"""
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import io
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.apps import apps
|
|
||||||
from django_gpg.exceptions import DecryptionError
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def document_pre_open_hook(file_object, instance):
|
|
||||||
logger.debug('instance: %s', instance)
|
|
||||||
|
|
||||||
DocumentVersionSignature = apps.get_model(
|
|
||||||
app_label='document_signatures', model_name='DocumentVersionSignature'
|
|
||||||
)
|
|
||||||
|
|
||||||
Key = apps.get_model(
|
|
||||||
app_label='django_gpg', model_name='Key'
|
|
||||||
)
|
|
||||||
|
|
||||||
if DocumentVersionSignature.objects.has_embedded_signature(document_version=instance):
|
|
||||||
# If it has an embedded signature, decrypt
|
|
||||||
try:
|
|
||||||
result = Key.objects.decrypt_file(file_object=file_object)
|
|
||||||
# gpg return a string, turn it into a file like object
|
|
||||||
except DecryptionError:
|
|
||||||
# At least return the original raw content
|
|
||||||
file_object.seek(0)
|
|
||||||
return file_object
|
|
||||||
else:
|
|
||||||
file_object.close()
|
|
||||||
return io.BytesIO(result)
|
|
||||||
else:
|
|
||||||
return file_object
|
|
||||||
|
|
||||||
|
|
||||||
def document_version_post_save_hook(instance):
|
|
||||||
logger.debug('instance: %s', instance)
|
|
||||||
|
|
||||||
DocumentVersionSignature = apps.get_model(
|
|
||||||
app_label='document_signatures', model_name='DocumentVersionSignature'
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
document_signature = DocumentVersionSignature.objects.get(
|
|
||||||
document_version=instance
|
|
||||||
)
|
|
||||||
except DocumentVersionSignature.DoesNotExist:
|
|
||||||
document_signature = DocumentVersionSignature.objects.create(
|
|
||||||
document_version=instance
|
|
||||||
)
|
|
||||||
document_signature.check_for_embedded_signature()
|
|
||||||
@@ -6,50 +6,57 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from navigation import Link
|
from navigation import Link
|
||||||
|
|
||||||
from .permissions import (
|
from .permissions import (
|
||||||
permission_document_verify, permission_signature_delete,
|
permission_document_version_signature_delete,
|
||||||
permission_signature_download, permission_signature_upload,
|
permission_document_version_signature_download,
|
||||||
|
permission_document_version_signature_upload,
|
||||||
|
permission_document_version_signature_verify,
|
||||||
|
permission_document_version_signature_view
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def can_upload_detached_signature(context):
|
def is_detached_signature(context):
|
||||||
DocumentVersionSignature = apps.get_model(
|
SignatureBaseModel = apps.get_model(
|
||||||
app_label='document_signatures', model_name='DocumentVersionSignature'
|
app_label='document_signatures', model_name='SignatureBaseModel'
|
||||||
)
|
)
|
||||||
|
|
||||||
return not DocumentVersionSignature.objects.has_detached_signature(
|
return SignatureBaseModel.objects.select_subclasses().get(
|
||||||
context['object'].latest_version
|
pk=context['object'].pk
|
||||||
) and not DocumentVersionSignature.objects.has_embedded_signature(
|
).is_detached
|
||||||
context['object'].latest_version
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def can_delete_detached_signature(context):
|
link_document_version_signature_delete = Link(
|
||||||
DocumentVersionSignature = apps.get_model(
|
condition=is_detached_signature,
|
||||||
app_label='document_signatures', model_name='DocumentVersionSignature'
|
#permissions=(permission_document_version_signature_delete,),
|
||||||
)
|
tags='dangerous', text=_('Delete'),
|
||||||
|
view='signatures:document_version_signature_delete',
|
||||||
return DocumentVersionSignature.objects.has_detached_signature(
|
args='resolved_object.pk'
|
||||||
context['object'].latest_version
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
link_document_signature_delete = Link(
|
|
||||||
condition=can_delete_detached_signature,
|
|
||||||
permissions=(permission_signature_delete,), tags='dangerous',
|
|
||||||
text=_('Delete signature'), view='signatures:document_signature_delete',
|
|
||||||
args='object.pk'
|
|
||||||
)
|
)
|
||||||
link_document_signature_download = Link(
|
link_document_version_signature_details = Link(
|
||||||
condition=can_delete_detached_signature, text=_('Download signature'),
|
#permissions=(permission_document_version_signature_view,),
|
||||||
view='signatures:document_signature_download', args='object.pk',
|
text=_('Details'),
|
||||||
permissions=(permission_signature_download,)
|
view='signatures:document_version_signature_details',
|
||||||
|
args='resolved_object.pk'
|
||||||
)
|
)
|
||||||
link_document_signature_upload = Link(
|
link_document_version_signature_list = Link(
|
||||||
condition=can_upload_detached_signature,
|
#permissions=(permission_document_version_signature_view,),
|
||||||
permissions=(permission_signature_upload,), text=_('Upload signature'),
|
text=_('Signature list'),
|
||||||
view='signatures:document_signature_upload', args='object.pk'
|
view='signatures:document_version_signature_list',
|
||||||
|
args='resolved_object.pk'
|
||||||
)
|
)
|
||||||
link_document_verify = Link(
|
link_document_version_signature_download = Link(
|
||||||
icon='fa fa-certificate', permissions=(permission_document_verify,),
|
condition=is_detached_signature,
|
||||||
text=_('Signatures'), view='signatures:document_verify', args='object.pk'
|
text=_('Download'),
|
||||||
|
view='signatures:document_signature_download', args='resolved_object.pk',
|
||||||
|
#permissions=(permission_document_version_signature_download,)
|
||||||
|
)
|
||||||
|
link_document_version_signature_upload = Link(
|
||||||
|
#permissions=(permission_document_version_signature_upload,),
|
||||||
|
text=_('Upload signature'), view='signatures:document_version_signature_upload',
|
||||||
|
args='resolved_object.pk'
|
||||||
|
)
|
||||||
|
link_document_version_signature_verify = Link(
|
||||||
|
icon='fa fa-certificate',
|
||||||
|
#permissions=(permission_document_version_signature_verify,),
|
||||||
|
text=_('Verify signatures'), view='signatures:document_verify',
|
||||||
|
args='resolved_object.pk'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,73 +4,66 @@ import logging
|
|||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from django_gpg.exceptions import VerificationError
|
from django_gpg.exceptions import DecryptionError, VerificationError
|
||||||
from django_gpg.models import Key
|
from django_gpg.models import Key
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DocumentVersionSignatureManager(models.Manager):
|
class DetachedSignatureManager(models.Manager):
|
||||||
def get_document_signature(self, document_version):
|
def upload_signature(self, document_version, signature_file):
|
||||||
document_signature, created = self.model.objects.get_or_create(
|
with document_version.open() as file_object:
|
||||||
document_version=document_version,
|
try:
|
||||||
)
|
verify_result = Key.objects.verify_file(
|
||||||
|
file_object=file_object, signature_file=signature_file
|
||||||
return document_signature
|
)
|
||||||
|
except VerificationError:
|
||||||
def add_detached_signature(self, document_version, detached_signature):
|
# Not signed
|
||||||
document_signature = self.get_document_signature(
|
pass
|
||||||
document_version=document_version
|
|
||||||
)
|
|
||||||
|
|
||||||
if document_signature.has_embedded_signature:
|
|
||||||
raise Exception(
|
|
||||||
'Document version already has an embedded signature'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if document_signature.signature_file:
|
|
||||||
logger.debug('Existing detached signature')
|
|
||||||
document_signature.delete_detached_signature_file()
|
|
||||||
document_signature.signature_file = None
|
|
||||||
document_signature.save()
|
|
||||||
|
|
||||||
document_signature.signature_file = detached_signature
|
|
||||||
document_signature.save()
|
|
||||||
|
|
||||||
def has_detached_signature(self, document_version):
|
|
||||||
try:
|
|
||||||
document_signature = self.get_document_signature(
|
|
||||||
document_version=document_version
|
|
||||||
)
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if document_signature.signature_file:
|
|
||||||
return True
|
|
||||||
else:
|
else:
|
||||||
return False
|
instance = self.create(
|
||||||
|
document_version=document_version,
|
||||||
|
date=verify_result.date,
|
||||||
|
key_id=verify_result.key_id,
|
||||||
|
signature_id=verify_result.signature_id,
|
||||||
|
public_key_fingerprint=verify_result.pubkey_fingerprint,
|
||||||
|
)
|
||||||
|
|
||||||
def has_embedded_signature(self, document_version):
|
|
||||||
logger.debug('document_version: %s', document_version)
|
|
||||||
|
|
||||||
try:
|
class EmbeddedSignatureManager(models.Manager):
|
||||||
document_signature = self.get_document_signature(
|
def check_signature(self, document_version):
|
||||||
document_version=document_version
|
logger.debug('checking for embedded signature')
|
||||||
)
|
|
||||||
except ValueError:
|
with document_version.open() as file_object:
|
||||||
return False
|
try:
|
||||||
|
verify_result = Key.objects.verify_file(file_object=file_object)
|
||||||
|
except VerificationError:
|
||||||
|
# Not signed
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
instance = self.create(
|
||||||
|
document_version=document_version,
|
||||||
|
date=verify_result.date,
|
||||||
|
key_id=verify_result.key_id,
|
||||||
|
signature_id=verify_result.signature_id,
|
||||||
|
public_key_fingerprint=verify_result.pubkey_fingerprint,
|
||||||
|
)
|
||||||
|
|
||||||
|
def open_signed(self, file_object, document_version):
|
||||||
|
for signature in self.filter(document_version=document_version):
|
||||||
|
try:
|
||||||
|
return self.open_signed(
|
||||||
|
file_object=Key.objects.decrypt_file(
|
||||||
|
file_object=file_object
|
||||||
|
), document_version=document_version
|
||||||
|
)
|
||||||
|
except DecryptionError:
|
||||||
|
file_object.seek(0)
|
||||||
|
return file_object
|
||||||
else:
|
else:
|
||||||
return document_signature.has_embedded_signature
|
return file_object
|
||||||
|
|
||||||
def detached_signature(self, document_version):
|
|
||||||
document_signature = self.get_document_signature(
|
|
||||||
document_version=document_version
|
|
||||||
)
|
|
||||||
|
|
||||||
return document_signature.signature_file.storage.open(
|
|
||||||
document_signature.signature_file.name
|
|
||||||
)
|
|
||||||
|
|
||||||
|
"""
|
||||||
def verify_signature(self, document_version):
|
def verify_signature(self, document_version):
|
||||||
document_version_descriptor = document_version.open(raw=True)
|
document_version_descriptor = document_version.open(raw=True)
|
||||||
detached_signature = None
|
detached_signature = None
|
||||||
@@ -91,14 +84,4 @@ class DocumentVersionSignatureManager(models.Manager):
|
|||||||
document_version_descriptor.close()
|
document_version_descriptor.close()
|
||||||
if detached_signature:
|
if detached_signature:
|
||||||
detached_signature.close()
|
detached_signature.close()
|
||||||
|
"""
|
||||||
def clear_detached_signature(self, document_version):
|
|
||||||
document_signature = self.get_document_signature(
|
|
||||||
document_version=document_version
|
|
||||||
)
|
|
||||||
if not document_signature.signature_file:
|
|
||||||
raise Exception('document doesn\'t have a detached signature')
|
|
||||||
|
|
||||||
document_signature.delete_detached_signature_file()
|
|
||||||
document_signature.signature_file = None
|
|
||||||
document_signature.save()
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import storage.backends.filebasedstorage
|
||||||
|
import document_signatures.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('documents', '0033_auto_20160325_0052'),
|
||||||
|
('document_signatures', '0002_auto_20150608_1902'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SignatureBaseModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('date', models.DateField(null=True, verbose_name='Date signed', blank=True)),
|
||||||
|
('key_id', models.CharField(max_length=40, verbose_name='Key ID')),
|
||||||
|
('signature_id', models.CharField(max_length=64, null=True, verbose_name='Signature ID', blank=True)),
|
||||||
|
('public_key_fingerprint', models.CharField(verbose_name='Public key fingerprint', unique=True, max_length=40, editable=False)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Document version signature',
|
||||||
|
'verbose_name_plural': 'Document version signatures',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='documentversionsignature',
|
||||||
|
name='has_embedded_signature',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='documentversionsignature',
|
||||||
|
name='date',
|
||||||
|
field=models.DateField(null=True, verbose_name='Date signed', blank=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='documentversionsignature',
|
||||||
|
name='signature_id',
|
||||||
|
field=models.CharField(max_length=64, null=True, verbose_name='Signature ID', blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='documentversionsignature',
|
||||||
|
name='document_version',
|
||||||
|
field=models.ForeignKey(related_name='signature', editable=False, to='documents.DocumentVersion', verbose_name='Document version'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DetachedSignature',
|
||||||
|
fields=[
|
||||||
|
('signaturebasemodel_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='document_signatures.SignatureBaseModel')),
|
||||||
|
('signature_file', models.FileField(storage=storage.backends.filebasedstorage.FileBasedStorage(), upload_to=document_signatures.models.upload_to, null=True, verbose_name='Signature file', blank=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Document version detached signature',
|
||||||
|
'verbose_name_plural': 'Document version detached signatures',
|
||||||
|
},
|
||||||
|
bases=('document_signatures.signaturebasemodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EmbeddedSignature',
|
||||||
|
fields=[
|
||||||
|
('signaturebasemodel_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='document_signatures.SignatureBaseModel')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Document version embedded signature',
|
||||||
|
'verbose_name_plural': 'Document version embedded signatures',
|
||||||
|
},
|
||||||
|
bases=('document_signatures.signaturebasemodel',),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='signaturebasemodel',
|
||||||
|
name='document_version',
|
||||||
|
field=models.ForeignKey(related_name='signaturebasemodel', editable=False, to='documents.DocumentVersion', verbose_name='Document version'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('document_signatures', '0003_auto_20160325_0052'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='documentversionsignature',
|
||||||
|
name='document_version',
|
||||||
|
field=models.ForeignKey(editable=False, to='documents.DocumentVersion', verbose_name='Document version'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='signaturebasemodel',
|
||||||
|
name='date',
|
||||||
|
field=models.DateField(verbose_name='Date signed', null=True, editable=False, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='signaturebasemodel',
|
||||||
|
name='document_version',
|
||||||
|
field=models.ForeignKey(related_name='signatures', editable=False, to='documents.DocumentVersion', verbose_name='Document version'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='signaturebasemodel',
|
||||||
|
name='public_key_fingerprint',
|
||||||
|
field=models.CharField(null=True, editable=False, max_length=40, blank=True, unique=True, verbose_name='Public key fingerprint'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='signaturebasemodel',
|
||||||
|
name='signature_id',
|
||||||
|
field=models.CharField(verbose_name='Signature ID', max_length=64, null=True, editable=False, blank=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('document_signatures', '0004_auto_20160325_0418'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='documentversionsignature',
|
||||||
|
name='document_version',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='DocumentVersionSignature',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from django_gpg.exceptions import DecryptionError
|
from model_utils.managers import InheritanceManager
|
||||||
|
|
||||||
|
from django_gpg.exceptions import DecryptionError, VerificationError
|
||||||
from django_gpg.models import Key
|
from django_gpg.models import Key
|
||||||
from documents.models import DocumentVersion
|
from documents.models import DocumentVersion
|
||||||
|
|
||||||
from .managers import DocumentVersionSignatureManager
|
from .managers import EmbeddedSignatureManager, DetachedSignatureManager
|
||||||
from .runtime import storage_backend
|
from .runtime import storage_backend
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -20,39 +25,71 @@ def upload_to(*args, **kwargs):
|
|||||||
return unicode(uuid.uuid4())
|
return unicode(uuid.uuid4())
|
||||||
|
|
||||||
|
|
||||||
class DocumentVersionSignature(models.Model):
|
@python_2_unicode_compatible
|
||||||
"""
|
class SignatureBaseModel(models.Model):
|
||||||
Model that describes a document version signature properties
|
|
||||||
"""
|
|
||||||
document_version = models.ForeignKey(
|
document_version = models.ForeignKey(
|
||||||
DocumentVersion, editable=False, verbose_name=_('Document version')
|
DocumentVersion, editable=False, related_name='signatures',
|
||||||
|
verbose_name=_('Document version')
|
||||||
)
|
)
|
||||||
signature_file = models.FileField(
|
# Basic fields
|
||||||
blank=True, null=True, storage=storage_backend, upload_to=upload_to,
|
date = models.DateField(
|
||||||
verbose_name=_('Signature file')
|
blank=True, editable=False, null=True, verbose_name=_('Date signed')
|
||||||
)
|
)
|
||||||
has_embedded_signature = models.BooleanField(
|
key_id = models.CharField(max_length=40, verbose_name=_('Key ID'))
|
||||||
default=False, verbose_name=_('Has embedded signature')
|
# With proper key
|
||||||
|
signature_id = models.CharField(
|
||||||
|
blank=True, editable=False, null=True, max_length=64,
|
||||||
|
verbose_name=_('Signature ID')
|
||||||
|
)
|
||||||
|
public_key_fingerprint = models.CharField(
|
||||||
|
blank=True, editable=False, null=True, max_length=40, unique=True,
|
||||||
|
verbose_name=_('Public key fingerprint')
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = DocumentVersionSignatureManager()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
def check_for_embedded_signature(self):
|
|
||||||
logger.debug('checking for embedded signature')
|
|
||||||
|
|
||||||
with self.document_version.open(raw=True) as file_object:
|
|
||||||
try:
|
|
||||||
Key.objects.decrypt_file(file_object=file_object)
|
|
||||||
except DecryptionError:
|
|
||||||
self.has_embedded_signature = False
|
|
||||||
else:
|
|
||||||
self.has_embedded_signature = True
|
|
||||||
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def delete_detached_signature_file(self):
|
|
||||||
self.signature_file.storage.delete(self.signature_file.name)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Document version signature')
|
verbose_name = _('Document version signature')
|
||||||
verbose_name_plural = _('Document version signatures')
|
verbose_name_plural = _('Document version signatures')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.signature_id or '{} - {}'.format(self.date, self.key_id)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse(
|
||||||
|
'document_signatures:document_version_signature_detail',
|
||||||
|
args=(self.pk,)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_detached(self):
|
||||||
|
return hasattr(self, 'signature_file')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_embedded(self):
|
||||||
|
return not hasattr(self, 'signature_file')
|
||||||
|
|
||||||
|
|
||||||
|
class EmbeddedSignature(SignatureBaseModel):
|
||||||
|
objects = EmbeddedSignatureManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Document version embedded signature')
|
||||||
|
verbose_name_plural = _('Document version embedded signatures')
|
||||||
|
|
||||||
|
|
||||||
|
class DetachedSignature(SignatureBaseModel):
|
||||||
|
signature_file = models.FileField(
|
||||||
|
blank=True, null=True, storage=storage_backend, upload_to=upload_to,
|
||||||
|
verbose_name=_('Signature file')
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = DetachedSignatureManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Document version detached signature')
|
||||||
|
verbose_name_plural = _('Document version detached signatures')
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
self.signature_file.storage.delete(self.signature_file.name)
|
||||||
|
super(DetachedSignature, self).delete(*args, **kwargs)
|
||||||
|
|||||||
@@ -8,15 +8,23 @@ namespace = PermissionNamespace(
|
|||||||
'document_signatures', _('Document signatures')
|
'document_signatures', _('Document signatures')
|
||||||
)
|
)
|
||||||
|
|
||||||
permission_document_verify = namespace.add_permission(
|
permission_document_version_signature_view = namespace.add_permission(
|
||||||
name='document_verify', label=_('Verify document signatures')
|
name='document_version_signature_view',
|
||||||
|
label=_('View details of document signatures')
|
||||||
)
|
)
|
||||||
permission_signature_delete = namespace.add_permission(
|
permission_document_version_signature_verify = namespace.add_permission(
|
||||||
name='signature_delete', label=_('Delete detached signatures')
|
name='document_version_signature_verify',
|
||||||
|
label=_('Verify document signatures')
|
||||||
)
|
)
|
||||||
permission_signature_download = namespace.add_permission(
|
permission_document_version_signature_delete = namespace.add_permission(
|
||||||
name='signature_download', label=_('Download detached signatures')
|
name='document_version_signature_delete',
|
||||||
|
label=_('Delete detached signatures')
|
||||||
)
|
)
|
||||||
permission_signature_upload = namespace.add_permission(
|
permission_document_version_signature_download = namespace.add_permission(
|
||||||
name='signature_upload', label=_('Upload detached signatures')
|
name='document_version_signature_download',
|
||||||
|
label=_('Download detached document signatures')
|
||||||
|
)
|
||||||
|
permission_document_version_signature_upload = namespace.add_permission(
|
||||||
|
name='document_version_signature_upload',
|
||||||
|
label=_('Upload detached document signatures')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ import os
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.files.base import File
|
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
|
||||||
from django_gpg.models import Key
|
from django_gpg.models import Key
|
||||||
from documents.models import DocumentType
|
from documents.models import DocumentType
|
||||||
from documents.tests import TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE
|
from documents.tests import TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE
|
||||||
|
|
||||||
from ..models import DocumentVersionSignature
|
from ..models import DetachedSignature, EmbeddedSignature
|
||||||
|
|
||||||
TEST_SIGNED_DOCUMENT_PATH = os.path.join(
|
TEST_SIGNED_DOCUMENT_PATH = os.path.join(
|
||||||
settings.BASE_DIR, 'contrib', 'sample_documents', 'mayan_11_1.pdf.gpg'
|
settings.BASE_DIR, 'contrib', 'sample_documents', 'mayan_11_1.pdf.gpg'
|
||||||
@@ -23,6 +22,7 @@ TEST_KEY_FILE = os.path.join(
|
|||||||
settings.BASE_DIR, 'contrib', 'sample_documents',
|
settings.BASE_DIR, 'contrib', 'sample_documents',
|
||||||
'key0x5F3F7F75D210724D.asc'
|
'key0x5F3F7F75D210724D.asc'
|
||||||
)
|
)
|
||||||
|
TEST_KEY_ID = '5F3F7F75D210724D'
|
||||||
|
|
||||||
|
|
||||||
@override_settings(OCR_AUTO_OCR=False)
|
@override_settings(OCR_AUTO_OCR=False)
|
||||||
@@ -32,28 +32,82 @@ class DocumentTestCase(TestCase):
|
|||||||
label=TEST_DOCUMENT_TYPE
|
label=TEST_DOCUMENT_TYPE
|
||||||
)
|
)
|
||||||
|
|
||||||
with open(TEST_DOCUMENT_PATH) as file_object:
|
|
||||||
self.document = self.document_type.new_document(
|
|
||||||
file_object=File(file_object), label='mayan_11_1.pdf'
|
|
||||||
)
|
|
||||||
|
|
||||||
with open(TEST_KEY_FILE) as file_object:
|
|
||||||
Key.objects.create(key_data=file_object.read())
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.document_type.delete()
|
self.document_type.delete()
|
||||||
|
|
||||||
def test_document_no_signature(self):
|
def test_embedded_signature(self):
|
||||||
self.assertEqual(
|
|
||||||
DocumentVersionSignature.objects.has_detached_signature(
|
|
||||||
self.document.latest_version
|
|
||||||
), False
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_new_document_version_signed(self):
|
|
||||||
with open(TEST_SIGNED_DOCUMENT_PATH) as file_object:
|
with open(TEST_SIGNED_DOCUMENT_PATH) as file_object:
|
||||||
self.document.new_version(
|
signed_document = self.document_type.new_document(
|
||||||
file_object=File(file_object), comment='test comment 1'
|
file_object=file_object
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(EmbeddedSignature.objects.count(), 1)
|
||||||
|
|
||||||
|
signature = EmbeddedSignature.objects.first()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
signature.document_version, signed_document.latest_version
|
||||||
|
)
|
||||||
|
self.assertEqual(signature.key_id, TEST_KEY_ID)
|
||||||
|
|
||||||
|
def test_embedded_signature_with_key(self):
|
||||||
|
with open(TEST_KEY_FILE) as file_object:
|
||||||
|
key = Key.objects.create(key_data=file_object.read())
|
||||||
|
|
||||||
|
with open(TEST_SIGNED_DOCUMENT_PATH) as file_object:
|
||||||
|
self.signed_document = self.document_type.new_document(
|
||||||
|
file_object=file_object
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(EmbeddedSignature.objects.count(), 1)
|
||||||
|
|
||||||
|
signature = EmbeddedSignature.objects.first()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
signature.document_version,
|
||||||
|
self.signed_document.latest_version
|
||||||
|
)
|
||||||
|
self.assertEqual(signature.key_id, TEST_KEY_ID)
|
||||||
|
self.assertEqual(signature.public_key_fingerprint, key.fingerprint)
|
||||||
|
|
||||||
|
def test_detached_signature(self):
|
||||||
|
with open(TEST_DOCUMENT_PATH) as file_object:
|
||||||
|
document = self.document_type.new_document(
|
||||||
|
file_object=file_object
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(TEST_SIGNATURE_FILE_PATH) as file_object:
|
||||||
|
DetachedSignature.objects.upload_signature(
|
||||||
|
document_version=document.latest_version,
|
||||||
|
signature_file=file_object
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(DetachedSignature.objects.count(), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
DetachedSignature.objects.first().document_version,
|
||||||
|
document.latest_version
|
||||||
|
)
|
||||||
|
self.assertEqual(DetachedSignature.objects.first().key_id, TEST_KEY_ID)
|
||||||
|
|
||||||
|
# TODO: test_verify_signature_after_new_key(self):
|
||||||
|
|
||||||
|
def test_document_no_signature(self):
|
||||||
|
with open(TEST_DOCUMENT_PATH) as file_object:
|
||||||
|
document = self.document_type.new_document(
|
||||||
|
file_object=file_object
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(EmbeddedSignature.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_new_signed_version(self):
|
||||||
|
with open(TEST_DOCUMENT_PATH) as file_object:
|
||||||
|
document = self.document_type.new_document(
|
||||||
|
file_object=file_object
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(TEST_SIGNED_DOCUMENT_PATH) as file_object:
|
||||||
|
signed_version = document.new_version(
|
||||||
|
file_object=file_object, comment='test comment 1'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Artifical delay since MySQL doesn't store microsecond data in
|
# Artifical delay since MySQL doesn't store microsecond data in
|
||||||
@@ -61,40 +115,9 @@ class DocumentTestCase(TestCase):
|
|||||||
# is the latest.
|
# is the latest.
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(EmbeddedSignature.objects.count(), 1)
|
||||||
DocumentVersionSignature.objects.has_detached_signature(
|
|
||||||
self.document.latest_version
|
|
||||||
), False
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
DocumentVersionSignature.objects.verify_signature(
|
|
||||||
self.document.latest_version
|
|
||||||
).status, SIGNATURE_STATE_VALID
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_detached_signatures(self):
|
signature = EmbeddedSignature.objects.first()
|
||||||
with open(TEST_DOCUMENT_PATH) as file_object:
|
|
||||||
self.document.new_version(
|
|
||||||
file_object=File(file_object), comment='test comment 2'
|
|
||||||
)
|
|
||||||
|
|
||||||
# GPGVerificationError
|
self.assertEqual(signature.document_version, signed_version)
|
||||||
self.assertEqual(DocumentVersionSignature.objects.verify_signature(
|
self.assertEqual(signature.key_id, TEST_KEY_ID)
|
||||||
self.document.latest_version), None
|
|
||||||
)
|
|
||||||
|
|
||||||
with open(TEST_SIGNATURE_FILE_PATH, 'rb') as file_object:
|
|
||||||
DocumentVersionSignature.objects.add_detached_signature(
|
|
||||||
self.document.latest_version, File(file_object)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
DocumentVersionSignature.objects.has_detached_signature(
|
|
||||||
self.document.latest_version
|
|
||||||
), True
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
DocumentVersionSignature.objects.verify_signature(
|
|
||||||
self.document.latest_version
|
|
||||||
).status, SIGNATURE_STATE_VALID
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -2,22 +2,40 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.conf.urls import patterns, url
|
from django.conf.urls import patterns, url
|
||||||
|
|
||||||
|
from .views import (
|
||||||
|
DocumentVersionSignatureDeleteView, DocumentVersionSignatureDetailView,
|
||||||
|
DocumentVersionSignatureListView
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
'document_signatures.views',
|
'document_signatures.views',
|
||||||
url(
|
url(
|
||||||
r'^verify/(?P<document_pk>\d+)/$', 'document_verify',
|
r'^(?P<pk>\d+)/details/$',
|
||||||
name='document_verify'
|
DocumentVersionSignatureDetailView.as_view(),
|
||||||
|
name='document_version_signature_details'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
r'^upload/signature/(?P<document_pk>\d+)/$',
|
r'^signature/(?P<pk>\d+)/download/$',
|
||||||
'document_signature_upload', name='document_signature_upload'
|
'document_signature_download',
|
||||||
|
name='document_version_signature_download'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
r'^download/signature/(?P<document_pk>\d+)/$',
|
r'^document/version/(?P<pk>\d+)/signatures/list/$',
|
||||||
'document_signature_download', name='document_signature_download'
|
DocumentVersionSignatureListView.as_view(),
|
||||||
|
name='document_version_signature_list'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
r'^document/(?P<document_pk>\d+)/signature/delete/$',
|
r'^documents/version/(?P<document_pk>\d+)/signature/verify/$',
|
||||||
'document_signature_delete', name='document_signature_delete'
|
'document_verify', name='document_version_signature_verify'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^documents/version/(?P<pk>\d+)/signature/upload/$',
|
||||||
|
'document_version_signature_upload',
|
||||||
|
name='document_version_signature_upload'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^signature/(?P<pk>\d+)/delete/$',
|
||||||
|
DocumentVersionSignatureDeleteView.as_view(),
|
||||||
|
name='document_version_signature_delete'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,21 +14,95 @@ from django.utils.html import escape
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from acls.models import AccessControlList
|
from acls.models import AccessControlList
|
||||||
|
from common.generics import (
|
||||||
|
SingleObjectDeleteView, SingleObjectDetailView, SingleObjectListView
|
||||||
|
)
|
||||||
from django_gpg.literals import SIGNATURE_STATE_NONE, SIGNATURE_STATES
|
from django_gpg.literals import SIGNATURE_STATE_NONE, SIGNATURE_STATES
|
||||||
from documents.models import Document
|
from documents.models import Document, DocumentVersion
|
||||||
from filetransfers.api import serve_file
|
from filetransfers.api import serve_file
|
||||||
from permissions import Permission
|
from permissions import Permission
|
||||||
|
|
||||||
from .forms import DetachedSignatureForm
|
from .forms import DetachedSignatureForm, DocumentVersionSignatureDetailForm
|
||||||
from .models import DocumentVersionSignature
|
from .models import DetachedSignature, SignatureBaseModel
|
||||||
from .permissions import (
|
from .permissions import (
|
||||||
permission_document_verify, permission_signature_upload,
|
permission_document_version_signature_view,
|
||||||
permission_signature_download, permission_signature_delete
|
permission_document_version_signature_verify,
|
||||||
|
permission_document_version_signature_upload,
|
||||||
|
permission_document_version_signature_download,
|
||||||
|
permission_document_version_signature_delete
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionSignatureDeleteView(SingleObjectDeleteView):
|
||||||
|
model = DetachedSignature
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'document': self.get_object().document_version.document,
|
||||||
|
'document_version': self.get_object().document_version,
|
||||||
|
'navigation_object_list': ('document', 'document_version', 'signature'),
|
||||||
|
'signature': self.get_object(),
|
||||||
|
'title': _('Delete detached signature: %s') % self.get_object()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_post_action_redirect(self):
|
||||||
|
return reverse(
|
||||||
|
'signatures:document_version_signature_list',
|
||||||
|
args=(self.get_object().document_version.pk,)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionSignatureDetailView(SingleObjectDetailView):
|
||||||
|
form_class = DocumentVersionSignatureDetailForm
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'document': self.get_object().document_version.document,
|
||||||
|
'document_version': self.get_object().document_version,
|
||||||
|
'signature': self.get_object(),
|
||||||
|
'navigation_object_list': ('document', 'document_version', 'signature'),
|
||||||
|
'hide_object': True,
|
||||||
|
'title': _(
|
||||||
|
'Details for signature: %s'
|
||||||
|
) % self.get_object(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return SignatureBaseModel.objects.select_subclasses()
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentVersionSignatureListView(SingleObjectListView):
|
||||||
|
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'),
|
||||||
|
'hide_object': True,
|
||||||
|
'title': _(
|
||||||
|
'Signatures for document version: %s'
|
||||||
|
) % self.get_document_version(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = self.get_document_version().signatures.all()
|
||||||
|
|
||||||
|
try:
|
||||||
|
Permission.check_permissions(
|
||||||
|
self.request.user, (permission_document_version_signature_view,)
|
||||||
|
)
|
||||||
|
except PermissionDenied:
|
||||||
|
return AccessControlList.objects.filter_by_access(
|
||||||
|
permission_document_version_signature_view, self.request.user, queryset
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
def document_verify(request, document_pk):
|
def document_verify(request, document_pk):
|
||||||
document = get_object_or_404(Document, pk=document_pk)
|
document = get_object_or_404(Document, pk=document_pk)
|
||||||
|
|
||||||
@@ -84,19 +158,20 @@ def document_verify(request, document_pk):
|
|||||||
}, context_instance=RequestContext(request))
|
}, context_instance=RequestContext(request))
|
||||||
|
|
||||||
|
|
||||||
def document_signature_upload(request, document_pk):
|
|
||||||
document = get_object_or_404(Document, pk=document_pk)
|
def document_version_signature_upload(request, pk):
|
||||||
|
document_version = get_object_or_404(DocumentVersion, pk=pk)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Permission.check_permissions(
|
Permission.check_permissions(
|
||||||
request.user, (permission_signature_upload,)
|
request.user, (permission_document_version_signature_upload,)
|
||||||
)
|
)
|
||||||
except PermissionDenied:
|
except PermissionDenied:
|
||||||
AccessControlList.objects.check_access(
|
AccessControlList.objects.check_access(
|
||||||
permission_signature_upload, request.user, document
|
permission_document_version_signature_upload, request.user, document_version.document
|
||||||
)
|
)
|
||||||
|
|
||||||
document.add_as_recent_document_for_user(request.user)
|
document_version.document.add_as_recent_document_for_user(request.user)
|
||||||
|
|
||||||
post_action_redirect = None
|
post_action_redirect = None
|
||||||
previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
||||||
@@ -106,8 +181,9 @@ def document_signature_upload(request, document_pk):
|
|||||||
form = DetachedSignatureForm(request.POST, request.FILES)
|
form = DetachedSignatureForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
try:
|
try:
|
||||||
DocumentVersionSignature.objects.add_detached_signature(
|
DetachedSignature.objects.upload_signature(
|
||||||
document.latest_version, request.FILES['file']
|
document_version=document_version,
|
||||||
|
signature_file=request.FILES['file']
|
||||||
)
|
)
|
||||||
messages.success(
|
messages.success(
|
||||||
request, _('Detached signature uploaded successfully.')
|
request, _('Detached signature uploaded successfully.')
|
||||||
@@ -122,9 +198,11 @@ def document_signature_upload(request, document_pk):
|
|||||||
return render_to_response('appearance/generic_form.html', {
|
return render_to_response('appearance/generic_form.html', {
|
||||||
'form': form,
|
'form': form,
|
||||||
'next': next,
|
'next': next,
|
||||||
'object': document,
|
'document': document_version.document,
|
||||||
|
'document_version': document_version,
|
||||||
|
'navigation_object_list': ('document', 'document_version'),
|
||||||
'previous': previous,
|
'previous': previous,
|
||||||
'title': _('Upload detached signature for document: %s') % document,
|
'title': _('Upload detached signature for document version: %s') % document_version,
|
||||||
}, context_instance=RequestContext(request))
|
}, context_instance=RequestContext(request))
|
||||||
|
|
||||||
|
|
||||||
@@ -133,11 +211,11 @@ def document_signature_download(request, document_pk):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
Permission.check_permissions(
|
Permission.check_permissions(
|
||||||
request.user, (permission_signature_download,)
|
request.user, (permission_document_version_signature_download,)
|
||||||
)
|
)
|
||||||
except PermissionDenied:
|
except PermissionDenied:
|
||||||
AccessControlList.objects.check_access(
|
AccessControlList.objects.check_access(
|
||||||
permission_signature_download, request.user, document
|
permission_document_version_signature_download, request.user, document
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -156,49 +234,3 @@ def document_signature_download(request, document_pk):
|
|||||||
return HttpResponseRedirect(request.META['HTTP_REFERER'])
|
return HttpResponseRedirect(request.META['HTTP_REFERER'])
|
||||||
|
|
||||||
return HttpResponseRedirect(request.META['HTTP_REFERER'])
|
return HttpResponseRedirect(request.META['HTTP_REFERER'])
|
||||||
|
|
||||||
|
|
||||||
def document_signature_delete(request, document_pk):
|
|
||||||
document = get_object_or_404(Document, pk=document_pk)
|
|
||||||
|
|
||||||
try:
|
|
||||||
Permission.check_permissions(
|
|
||||||
request.user, (permission_signature_delete,)
|
|
||||||
)
|
|
||||||
except PermissionDenied:
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
permission_signature_delete, request.user, document
|
|
||||||
)
|
|
||||||
|
|
||||||
document.add_as_recent_document_for_user(request.user)
|
|
||||||
|
|
||||||
post_action_redirect = None
|
|
||||||
previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
|
||||||
next = request.POST.get('next', request.GET.get('next', post_action_redirect if post_action_redirect else request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
try:
|
|
||||||
DocumentVersionSignature.objects.clear_detached_signature(
|
|
||||||
document.latest_version
|
|
||||||
)
|
|
||||||
messages.success(
|
|
||||||
request, _('Detached signature deleted successfully.')
|
|
||||||
)
|
|
||||||
return HttpResponseRedirect(next)
|
|
||||||
except Exception as exception:
|
|
||||||
messages.error(
|
|
||||||
request, _(
|
|
||||||
'Error while deleting the detached signature; %s'
|
|
||||||
) % exception
|
|
||||||
)
|
|
||||||
return HttpResponseRedirect(previous)
|
|
||||||
|
|
||||||
return render_to_response('appearance/generic_confirm.html', {
|
|
||||||
'delete_view': True,
|
|
||||||
'next': next,
|
|
||||||
'object': document,
|
|
||||||
'previous': previous,
|
|
||||||
'title': _(
|
|
||||||
'Delete the detached signature from document: %s?'
|
|
||||||
) % document,
|
|
||||||
}, context_instance=RequestContext(request))
|
|
||||||
|
|||||||
18
mayan/apps/documents/migrations/0033_auto_20160325_0052.py
Normal file
18
mayan/apps/documents/migrations/0033_auto_20160325_0052.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('documents', '0032_auto_20160315_0537'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='documenttypefilename',
|
||||||
|
options={'ordering': ('filename',), 'verbose_name': 'Quick label', 'verbose_name_plural': 'Quick labels'},
|
||||||
|
),
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user