Start of document_signatures app refactor.

This commit is contained in:
Roberto Rosario
2016-03-25 04:07:57 -04:00
parent ab6e2d8c23
commit 355190e919
16 changed files with 650 additions and 343 deletions

View File

@@ -27,6 +27,7 @@
- More tests added.
- Handle unicode filenames in staging folders.
- Add staging file deletion permission.
- New document_signature_view permission.
2.0.2 (2016-02-09)
==================

View File

@@ -2,9 +2,9 @@ from __future__ import unicode_literals
from django.contrib import admin
from .models import DocumentVersionSignature
#from .models import DocumentVersionSignature
"""
@admin.register(DocumentVersionSignature)
class DocumentVersionSignatureAdmin(admin.ModelAdmin):
def document(self, instance):
@@ -20,3 +20,4 @@ class DocumentVersionSignatureAdmin(admin.ModelAdmin):
)
list_display_links = ('document_version',)
search_fields = ('document_version__document__label',)
"""

View File

@@ -6,16 +6,26 @@ from django.apps import apps
from django.utils.translation import ugettext_lazy as _
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 (
link_document_signature_delete, link_document_signature_download,
link_document_signature_upload, link_document_verify
link_document_version_signature_delete,
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 (
permission_document_verify, permission_signature_delete,
permission_signature_download, permission_signature_upload
permission_document_version_signature_delete,
permission_document_version_signature_download,
permission_document_version_signature_upload,
permission_document_version_signature_verify,
permission_document_version_signature_view,
)
logger = logging.getLogger(__name__)
@@ -39,30 +49,74 @@ class DocumentSignaturesApp(MayanAppConfig):
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(
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(
model=Document, permissions=(
permission_document_verify, permission_signature_delete,
permission_signature_download, permission_signature_upload,
permission_document_version_signature_delete,
permission_document_version_signature_download,
permission_document_version_signature_verify,
permission_document_version_signature_view,
permission_document_version_signature_upload,
)
)
menu_facet.bind_links(
links=(link_document_verify,), sources=(Document,)
SourceColumn(
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(
links=(
link_document_signature_upload,
link_document_signature_download,
link_document_signature_delete
), sources=(
'signatures:document_verify',
'signatures:document_signature_upload',
'signatures:document_signature_download',
'signatures:document_signature_delete'
)
link_document_version_signature_upload,
link_document_version_signature_verify,
), sources=(DocumentVersion,)
)

View File

@@ -3,8 +3,49 @@ from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy as _
from common.forms import DetailForm
from .models import SignatureBaseModel
class DetachedSignatureForm(forms.Form):
file = forms.FileField(
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()},
"""

View File

@@ -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()

View File

@@ -6,50 +6,57 @@ from django.utils.translation import ugettext_lazy as _
from navigation import Link
from .permissions import (
permission_document_verify, permission_signature_delete,
permission_signature_download, permission_signature_upload,
permission_document_version_signature_delete,
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):
DocumentVersionSignature = apps.get_model(
app_label='document_signatures', model_name='DocumentVersionSignature'
def is_detached_signature(context):
SignatureBaseModel = apps.get_model(
app_label='document_signatures', model_name='SignatureBaseModel'
)
return not DocumentVersionSignature.objects.has_detached_signature(
context['object'].latest_version
) and not DocumentVersionSignature.objects.has_embedded_signature(
context['object'].latest_version
)
return SignatureBaseModel.objects.select_subclasses().get(
pk=context['object'].pk
).is_detached
def can_delete_detached_signature(context):
DocumentVersionSignature = apps.get_model(
app_label='document_signatures', model_name='DocumentVersionSignature'
link_document_version_signature_delete = Link(
condition=is_detached_signature,
#permissions=(permission_document_version_signature_delete,),
tags='dangerous', text=_('Delete'),
view='signatures:document_version_signature_delete',
args='resolved_object.pk'
)
return DocumentVersionSignature.objects.has_detached_signature(
context['object'].latest_version
link_document_version_signature_details = Link(
#permissions=(permission_document_version_signature_view,),
text=_('Details'),
view='signatures:document_version_signature_details',
args='resolved_object.pk'
)
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_version_signature_list = Link(
#permissions=(permission_document_version_signature_view,),
text=_('Signature list'),
view='signatures:document_version_signature_list',
args='resolved_object.pk'
)
link_document_signature_download = Link(
condition=can_delete_detached_signature, text=_('Download signature'),
view='signatures:document_signature_download', args='object.pk',
permissions=(permission_signature_download,)
link_document_version_signature_download = Link(
condition=is_detached_signature,
text=_('Download'),
view='signatures:document_signature_download', args='resolved_object.pk',
#permissions=(permission_document_version_signature_download,)
)
link_document_signature_upload = Link(
condition=can_upload_detached_signature,
permissions=(permission_signature_upload,), text=_('Upload signature'),
view='signatures:document_signature_upload', args='object.pk'
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_verify = Link(
icon='fa fa-certificate', permissions=(permission_document_verify,),
text=_('Signatures'), view='signatures:document_verify', args='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'
)

View File

@@ -4,73 +4,66 @@ import logging
from django.db import models
from django_gpg.exceptions import VerificationError
from django_gpg.exceptions import DecryptionError, VerificationError
from django_gpg.models import Key
logger = logging.getLogger(__name__)
class DocumentVersionSignatureManager(models.Manager):
def get_document_signature(self, document_version):
document_signature, created = self.model.objects.get_or_create(
class DetachedSignatureManager(models.Manager):
def upload_signature(self, document_version, signature_file):
with document_version.open() as file_object:
try:
verify_result = Key.objects.verify_file(
file_object=file_object, signature_file=signature_file
)
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,
)
return document_signature
def add_detached_signature(self, document_version, detached_signature):
document_signature = self.get_document_signature(
document_version=document_version
)
class EmbeddedSignatureManager(models.Manager):
def check_signature(self, document_version):
logger.debug('checking for embedded signature')
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):
with document_version.open() as file_object:
try:
document_signature = self.get_document_signature(
document_version=document_version
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,
)
except ValueError:
return False
else:
if document_signature.signature_file:
return True
else:
return False
def has_embedded_signature(self, document_version):
logger.debug('document_version: %s', document_version)
def open_signed(self, file_object, document_version):
for signature in self.filter(document_version=document_version):
try:
document_signature = self.get_document_signature(
document_version=document_version
return self.open_signed(
file_object=Key.objects.decrypt_file(
file_object=file_object
), document_version=document_version
)
except ValueError:
return False
except DecryptionError:
file_object.seek(0)
return file_object
else:
return document_signature.has_embedded_signature
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
)
return file_object
"""
def verify_signature(self, document_version):
document_version_descriptor = document_version.open(raw=True)
detached_signature = None
@@ -91,14 +84,4 @@ class DocumentVersionSignatureManager(models.Manager):
document_version_descriptor.close()
if detached_signature:
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()
"""

View File

@@ -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'),
),
]

View File

@@ -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),
),
]

View File

@@ -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',
),
]

View File

@@ -1,16 +1,21 @@
from __future__ import unicode_literals
from datetime import date
import logging
import uuid
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
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 documents.models import DocumentVersion
from .managers import DocumentVersionSignatureManager
from .managers import EmbeddedSignatureManager, DetachedSignatureManager
from .runtime import storage_backend
logger = logging.getLogger(__name__)
@@ -20,39 +25,71 @@ def upload_to(*args, **kwargs):
return unicode(uuid.uuid4())
class DocumentVersionSignature(models.Model):
"""
Model that describes a document version signature properties
"""
@python_2_unicode_compatible
class SignatureBaseModel(models.Model):
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(
blank=True, null=True, storage=storage_backend, upload_to=upload_to,
verbose_name=_('Signature file')
# Basic fields
date = models.DateField(
blank=True, editable=False, null=True, verbose_name=_('Date signed')
)
has_embedded_signature = models.BooleanField(
default=False, verbose_name=_('Has embedded signature')
key_id = models.CharField(max_length=40, verbose_name=_('Key ID'))
# 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()
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)
objects = InheritanceManager()
class Meta:
verbose_name = _('Document version signature')
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)

View File

@@ -8,15 +8,23 @@ namespace = PermissionNamespace(
'document_signatures', _('Document signatures')
)
permission_document_verify = namespace.add_permission(
name='document_verify', label=_('Verify document signatures')
permission_document_version_signature_view = namespace.add_permission(
name='document_version_signature_view',
label=_('View details of document signatures')
)
permission_signature_delete = namespace.add_permission(
name='signature_delete', label=_('Delete detached signatures')
permission_document_version_signature_verify = namespace.add_permission(
name='document_version_signature_verify',
label=_('Verify document signatures')
)
permission_signature_download = namespace.add_permission(
name='signature_download', label=_('Download detached signatures')
permission_document_version_signature_delete = namespace.add_permission(
name='document_version_signature_delete',
label=_('Delete detached signatures')
)
permission_signature_upload = namespace.add_permission(
name='signature_upload', label=_('Upload detached signatures')
permission_document_version_signature_download = namespace.add_permission(
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')
)

View File

@@ -4,14 +4,13 @@ import os
import time
from django.conf import settings
from django.core.files.base import File
from django.test import TestCase, override_settings
from django_gpg.models import Key
from documents.models import DocumentType
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(
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',
'key0x5F3F7F75D210724D.asc'
)
TEST_KEY_ID = '5F3F7F75D210724D'
@override_settings(OCR_AUTO_OCR=False)
@@ -32,28 +32,82 @@ class DocumentTestCase(TestCase):
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):
self.document_type.delete()
def test_document_no_signature(self):
self.assertEqual(
DocumentVersionSignature.objects.has_detached_signature(
self.document.latest_version
), False
def test_embedded_signature(self):
with open(TEST_SIGNED_DOCUMENT_PATH) as file_object:
signed_document = self.document_type.new_document(
file_object=file_object
)
def test_new_document_version_signed(self):
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.document.new_version(
file_object=File(file_object), comment='test comment 1'
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
@@ -61,40 +115,9 @@ class DocumentTestCase(TestCase):
# is the latest.
time.sleep(2)
self.assertEqual(
DocumentVersionSignature.objects.has_detached_signature(
self.document.latest_version
), False
)
self.assertEqual(
DocumentVersionSignature.objects.verify_signature(
self.document.latest_version
).status, SIGNATURE_STATE_VALID
)
self.assertEqual(EmbeddedSignature.objects.count(), 1)
def test_detached_signatures(self):
with open(TEST_DOCUMENT_PATH) as file_object:
self.document.new_version(
file_object=File(file_object), comment='test comment 2'
)
signature = EmbeddedSignature.objects.first()
# GPGVerificationError
self.assertEqual(DocumentVersionSignature.objects.verify_signature(
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
)
self.assertEqual(signature.document_version, signed_version)
self.assertEqual(signature.key_id, TEST_KEY_ID)

View File

@@ -2,22 +2,40 @@ from __future__ import unicode_literals
from django.conf.urls import patterns, url
from .views import (
DocumentVersionSignatureDeleteView, DocumentVersionSignatureDetailView,
DocumentVersionSignatureListView
)
urlpatterns = patterns(
'document_signatures.views',
url(
r'^verify/(?P<document_pk>\d+)/$', 'document_verify',
name='document_verify'
r'^(?P<pk>\d+)/details/$',
DocumentVersionSignatureDetailView.as_view(),
name='document_version_signature_details'
),
url(
r'^upload/signature/(?P<document_pk>\d+)/$',
'document_signature_upload', name='document_signature_upload'
r'^signature/(?P<pk>\d+)/download/$',
'document_signature_download',
name='document_version_signature_download'
),
url(
r'^download/signature/(?P<document_pk>\d+)/$',
'document_signature_download', name='document_signature_download'
r'^document/version/(?P<pk>\d+)/signatures/list/$',
DocumentVersionSignatureListView.as_view(),
name='document_version_signature_list'
),
url(
r'^document/(?P<document_pk>\d+)/signature/delete/$',
'document_signature_delete', name='document_signature_delete'
r'^documents/version/(?P<document_pk>\d+)/signature/verify/$',
'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'
),
)

View File

@@ -14,21 +14,95 @@ from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _
from acls.models import AccessControlList
from common.generics import (
SingleObjectDeleteView, SingleObjectDetailView, SingleObjectListView
)
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 permissions import Permission
from .forms import DetachedSignatureForm
from .models import DocumentVersionSignature
from .forms import DetachedSignatureForm, DocumentVersionSignatureDetailForm
from .models import DetachedSignature, SignatureBaseModel
from .permissions import (
permission_document_verify, permission_signature_upload,
permission_signature_download, permission_signature_delete
permission_document_version_signature_view,
permission_document_version_signature_verify,
permission_document_version_signature_upload,
permission_document_version_signature_download,
permission_document_version_signature_delete
)
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):
document = get_object_or_404(Document, pk=document_pk)
@@ -84,19 +158,20 @@ def document_verify(request, document_pk):
}, 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:
Permission.check_permissions(
request.user, (permission_signature_upload,)
request.user, (permission_document_version_signature_upload,)
)
except PermissionDenied:
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
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)
if form.is_valid():
try:
DocumentVersionSignature.objects.add_detached_signature(
document.latest_version, request.FILES['file']
DetachedSignature.objects.upload_signature(
document_version=document_version,
signature_file=request.FILES['file']
)
messages.success(
request, _('Detached signature uploaded successfully.')
@@ -122,9 +198,11 @@ def document_signature_upload(request, document_pk):
return render_to_response('appearance/generic_form.html', {
'form': form,
'next': next,
'object': document,
'document': document_version.document,
'document_version': document_version,
'navigation_object_list': ('document', 'document_version'),
'previous': previous,
'title': _('Upload detached signature for document: %s') % document,
'title': _('Upload detached signature for document version: %s') % document_version,
}, context_instance=RequestContext(request))
@@ -133,11 +211,11 @@ def document_signature_download(request, document_pk):
try:
Permission.check_permissions(
request.user, (permission_signature_download,)
request.user, (permission_document_version_signature_download,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_signature_download, request.user, document
permission_document_version_signature_download, request.user, document
)
try:
@@ -156,49 +234,3 @@ def document_signature_download(request, document_pk):
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))

View 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'},
),
]