Merge branch 'feature/embedded_signature_handling' into development
This commit is contained in:
@@ -31,6 +31,41 @@ KEY_SECONDARY_CLASSES = (
|
||||
((KEY_CLASS_ELG), _(u'Elgamal')),
|
||||
)
|
||||
|
||||
SIGNATURE_STATE_BAD = 'signature bad'
|
||||
SIGNATURE_STATE_NONE = None
|
||||
SIGNATURE_STATE_ERROR = 'signature error'
|
||||
SIGNATURE_STATE_NO_PUBLIC_KEY = 'no public key'
|
||||
SIGNATURE_STATE_GOOD = 'signature good'
|
||||
SIGNATURE_STATE_VALID = 'signature valid'
|
||||
|
||||
SIGNATURE_STATES = {
|
||||
SIGNATURE_STATE_BAD: {
|
||||
'text': _(u'Bad signature.'),
|
||||
'icon': 'cross.png'
|
||||
},
|
||||
SIGNATURE_STATE_NONE: {
|
||||
'text': _(u'Document not signed or invalid signature.'),
|
||||
'icon': 'cross.png'
|
||||
},
|
||||
SIGNATURE_STATE_ERROR: {
|
||||
'text': _(u'Signature error.'),
|
||||
'icon': 'cross.png'
|
||||
},
|
||||
SIGNATURE_STATE_NO_PUBLIC_KEY: {
|
||||
'text': _(u'Document is signed but no public key is available for verification.'),
|
||||
'icon': 'user_silhouette.png'
|
||||
},
|
||||
SIGNATURE_STATE_GOOD: {
|
||||
'text': _(u'Document is signed, and signature is good.'),
|
||||
'icon': 'document_signature.png'
|
||||
},
|
||||
SIGNATURE_STATE_VALID: {
|
||||
'text': _(u'Document is signed with a valid signature.'),
|
||||
'icon': 'document_signature.png'
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class Key(object):
|
||||
@staticmethod
|
||||
def get_key_id(fingerprint):
|
||||
@@ -163,9 +198,9 @@ class GPG(object):
|
||||
|
||||
if verify:
|
||||
return verify
|
||||
elif getattr(verify, 'status', None) == 'no public key':
|
||||
# Exception to the rule, to be able to query the keyservers
|
||||
return verify
|
||||
#elif getattr(verify, 'status', None) == 'no public key':
|
||||
# # Exception to the rule, to be able to query the keyservers
|
||||
# return verify
|
||||
else:
|
||||
raise GPGVerificationError()
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from django.template.defaultfilters import force_escape
|
||||
from documents.models import Document, RecentDocument
|
||||
from permissions.api import check_permissions
|
||||
|
||||
from django_gpg.api import Key
|
||||
from django_gpg.api import Key, SIGNATURE_STATES
|
||||
from django_gpg.runtime import gpg
|
||||
from django_gpg.exceptions import GPGVerificationError
|
||||
from django_gpg import PERMISSION_DOCUMENT_VERIFY
|
||||
@@ -57,48 +57,27 @@ def document_verify(request, document_pk):
|
||||
|
||||
RecentDocument.objects.add_document_for_user(request.user, document)
|
||||
try:
|
||||
signature = gpg.verify_w_retry(document.open())
|
||||
signature = gpg.verify_w_retry(document.open(raw=True))
|
||||
except GPGVerificationError:
|
||||
signature = None
|
||||
|
||||
signature_states = {
|
||||
'signature bad': {
|
||||
'text': _(u'Bad signature.'),
|
||||
'icon': 'cross.png'
|
||||
},
|
||||
None: {
|
||||
'text': _(u'Document not signed or invalid signature.'),
|
||||
'icon': 'cross.png'
|
||||
},
|
||||
'signature error': {
|
||||
'text': _(u'Signature error.'),
|
||||
'icon': 'cross.png'
|
||||
},
|
||||
'no public key': {
|
||||
'text': _(u'Document is signed but no public key is available for verification.'),
|
||||
'icon': 'user_silhouette.png'
|
||||
},
|
||||
'signature good': {
|
||||
'text': _(u'Document is signed, and signature is good.'),
|
||||
'icon': 'document_signature.png'
|
||||
},
|
||||
'signature valid': {
|
||||
'text': _(u'Document is signed with a valid signature.'),
|
||||
'icon': 'document_signature.png'
|
||||
},
|
||||
}
|
||||
|
||||
signature_state = signature_states.get(getattr(signature, 'status', None))
|
||||
signature_state = SIGNATURE_STATES.get(getattr(signature, 'status', None))
|
||||
|
||||
widget = (u'<img style="vertical-align: middle;" src="%simages/icons/%s" />' % (settings.STATIC_URL, signature_state['icon']))
|
||||
paragraphs = [
|
||||
_(u'Signature status: %s %s') % (mark_safe(widget), signature_state['text']),
|
||||
]
|
||||
|
||||
if document.signature_state:
|
||||
signature_type = _(u'embedded')
|
||||
else:
|
||||
signature_type = _(u'detached')
|
||||
|
||||
if signature:
|
||||
paragraphs.extend(
|
||||
[
|
||||
_(u'Signature ID: %s') % signature.signature_id,
|
||||
_(u'Signature type: %s') % signature_type,
|
||||
_(u'Key ID: %s') % signature.key_id,
|
||||
_(u'Timestamp: %s') % datetime.fromtimestamp(int(signature.sig_timestamp)),
|
||||
_(u'Signee: %s') % force_escape(getattr(signature, 'username', u'')),
|
||||
|
||||
@@ -204,8 +204,8 @@ register_top_menu(
|
||||
children_path_regex=[
|
||||
r'^documents/[^t]', r'^metadata/[^s]', r'comments', r'tags/document', r'grouping/[^s]', r'history/list/for_object/documents'
|
||||
],
|
||||
children_view_regex=[r'upload'],
|
||||
children_views=['document_folder_list', 'folder_add_document', 'document_index_list'],
|
||||
#children_view_regex=[r'upload'],
|
||||
children_views=['document_folder_list', 'folder_add_document', 'document_index_list', 'upload_version',],
|
||||
position=1
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Adding field 'DocumentVersion.signature_state'
|
||||
db.add_column('documents_documentversion', 'signature_state', self.gf('django.db.models.fields.CharField')(max_length=16, null=True, blank=True), keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Deleting field 'DocumentVersion.signature_state'
|
||||
db.delete_column('documents_documentversion', 'signature_state')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'comments.comment': {
|
||||
'Meta': {'ordering': "('submit_date',)", 'object_name': 'Comment', 'db_table': "'django_comments'"},
|
||||
'comment': ('django.db.models.fields.TextField', [], {'max_length': '3000'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_comment'", 'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}),
|
||||
'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_removed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'object_pk': ('django.db.models.fields.TextField', [], {}),
|
||||
'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}),
|
||||
'submit_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comment_comments'", 'null': 'True', 'to': "orm['auth.User']"}),
|
||||
'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'user_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
|
||||
'user_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'documents.document': {
|
||||
'Meta': {'ordering': "['-date_added']", 'object_name': 'Document'},
|
||||
'date_added': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
|
||||
'document_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentType']", 'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'uuid': ('django.db.models.fields.CharField', [], {'max_length': '48', 'blank': 'True'})
|
||||
},
|
||||
'documents.documentpage': {
|
||||
'Meta': {'ordering': "['page_number']", 'object_name': 'DocumentPage'},
|
||||
'content': ('django.db.models.fields.TextField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
|
||||
'document_version': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentVersion']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'page_label': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
|
||||
'page_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1', 'db_index': 'True'})
|
||||
},
|
||||
'documents.documentpagetransformation': {
|
||||
'Meta': {'ordering': "('order',)", 'object_name': 'DocumentPageTransformation'},
|
||||
'arguments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'document_page': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentPage']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'transformation': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||
},
|
||||
'documents.documenttype': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'DocumentType'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '32'})
|
||||
},
|
||||
'documents.documenttypefilename': {
|
||||
'Meta': {'ordering': "['filename']", 'object_name': 'DocumentTypeFilename'},
|
||||
'document_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentType']"}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'filename': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'documents.documentversion': {
|
||||
'Meta': {'unique_together': "(('document', 'major', 'minor', 'micro', 'release_level', 'serial'),)", 'object_name': 'DocumentVersion'},
|
||||
'checksum': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.Document']"}),
|
||||
'encoding': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64'}),
|
||||
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
|
||||
'filename': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'major': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
|
||||
'micro': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'mimetype': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64'}),
|
||||
'minor': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'release_level': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
|
||||
'serial': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||
'signature_state': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True', 'blank': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {})
|
||||
},
|
||||
'documents.recentdocument': {
|
||||
'Meta': {'ordering': "('-datetime_accessed',)", 'object_name': 'RecentDocument'},
|
||||
'datetime_accessed': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.Document']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'sites.site': {
|
||||
'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"},
|
||||
'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'taggit.tag': {
|
||||
'Meta': {'object_name': 'Tag'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'})
|
||||
},
|
||||
'taggit.taggeditem': {
|
||||
'Meta': {'object_name': 'TaggedItem'},
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
|
||||
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['documents']
|
||||
@@ -21,10 +21,12 @@ from converter.api import get_page_count
|
||||
from converter.api import get_available_transformations_choices
|
||||
from converter.api import convert
|
||||
from converter.exceptions import UnknownFileFormat, UnkownConvertError
|
||||
from mimetype.api import get_mimetype, get_icon_file_path, \
|
||||
get_error_icon_file_path
|
||||
from mimetype.api import (get_mimetype, get_icon_file_path,
|
||||
get_error_icon_file_path, get_encoding)
|
||||
from converter.literals import (DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION,
|
||||
DEFAULT_PAGE_NUMBER)
|
||||
from django_gpg.runtime import gpg
|
||||
from django_gpg.exceptions import GPGVerificationError, GPGDecryptionError
|
||||
|
||||
from documents.conf.settings import CHECKSUM_FUNCTION
|
||||
from documents.conf.settings import UUID_FUNCTION
|
||||
@@ -209,12 +211,12 @@ class Document(models.Model):
|
||||
return new_version
|
||||
|
||||
# Proxy methods
|
||||
def open(self):
|
||||
def open(self, *args, **kwargs):
|
||||
'''
|
||||
Return a file descriptor to a document's file irrespective of
|
||||
the storage backend
|
||||
'''
|
||||
return self.latest_version.open()
|
||||
return self.latest_version.open(*args, **kwargs)
|
||||
|
||||
def save_to_file(self, *args, **kwargs):
|
||||
return self.latest_version.save_to_file(*args, **kwargs)
|
||||
@@ -251,6 +253,10 @@ class Document(models.Model):
|
||||
def checksum(self):
|
||||
return self.latest_version.checksum
|
||||
|
||||
@property
|
||||
def signature_state(self):
|
||||
return self.latest_version.signature_state
|
||||
|
||||
@property
|
||||
def pages(self):
|
||||
return self.latest_version.pages
|
||||
@@ -309,7 +315,8 @@ class DocumentVersion(models.Model):
|
||||
encoding = models.CharField(max_length=64, default='', editable=False)
|
||||
filename = models.CharField(max_length=255, default=u'', editable=False, db_index=True)
|
||||
checksum = models.TextField(blank=True, null=True, verbose_name=_(u'checksum'), editable=False)
|
||||
|
||||
signature_state = models.CharField(blank=True, null=True, max_length=16, verbose_name=_(u'signature state'), editable=False)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('document', 'major', 'minor', 'micro', 'release_level', 'serial')
|
||||
verbose_name = _(u'document version')
|
||||
@@ -371,6 +378,7 @@ class DocumentVersion(models.Model):
|
||||
|
||||
if new_document:
|
||||
#Only do this for new documents
|
||||
self.update_signed_state(save=False)
|
||||
self.update_checksum(save=False)
|
||||
self.update_mimetype(save=False)
|
||||
self.save()
|
||||
@@ -443,6 +451,18 @@ class DocumentVersion(models.Model):
|
||||
'''
|
||||
for version in self.document.versions.filter(timestamp__gt=self.timestamp):
|
||||
version.delete()
|
||||
|
||||
def update_signed_state(self, save=True):
|
||||
if self.exists():
|
||||
try:
|
||||
self.signature_state = gpg.verify_w_retry(self.open()).status
|
||||
# TODO: give use choice for auto public key fetch?
|
||||
# OR maybe new config option
|
||||
except GPGVerificationError:
|
||||
self.signature_state = None
|
||||
|
||||
if save:
|
||||
self.save()
|
||||
|
||||
def update_mimetype(self, save=True):
|
||||
'''
|
||||
@@ -470,12 +490,24 @@ class DocumentVersion(models.Model):
|
||||
'''
|
||||
return self.file.storage.exists(self.file.path)
|
||||
|
||||
def open(self):
|
||||
def open(self, raw=False):
|
||||
'''
|
||||
Return a file descriptor to a document version's file irrespective of
|
||||
the storage backend
|
||||
'''
|
||||
return self.file.storage.open(self.file.path)
|
||||
if self.signature_state and not raw:
|
||||
try:
|
||||
result = gpg.decrypt_file(self.file.storage.open(self.file.path))
|
||||
# gpg return a string, turn it into a file like object
|
||||
container = StringIO()
|
||||
container.write(result.data)
|
||||
container.seek(0)
|
||||
return container
|
||||
except GPGDecryptionError:
|
||||
# At least return the original raw content
|
||||
return self.file.storage.open(self.file.path)
|
||||
else:
|
||||
return self.file.storage.open(self.file.path)
|
||||
|
||||
def save_to_file(self, filepath, buffer_size=1024 * 1024):
|
||||
'''
|
||||
|
||||
Reference in New Issue
Block a user