Add document signature verification app
This commit is contained in:
19
apps/django_gpg/__init__.py
Normal file
19
apps/django_gpg/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from documents.models import Document
|
||||
from navigation.api import register_links, register_top_menu, \
|
||||
register_model_list_columns, register_multi_item_links, \
|
||||
register_sidebar_template
|
||||
from main.api import register_diagnostic, register_maintenance_links
|
||||
from permissions.api import register_permission, set_namespace_title
|
||||
from project_setup.api import register_setup
|
||||
|
||||
PERMISSION_DOCUMENT_VERIFY = {'namespace': 'django_gpg', 'name': 'document_verify', 'label': _(u'Verify document signatures')}
|
||||
|
||||
# Permission setup
|
||||
set_namespace_title('django_gpg', _(u'Signatures'))
|
||||
register_permission(PERMISSION_DOCUMENT_VERIFY)
|
||||
|
||||
document_verify = {'text': _(u'Signatures'), 'view': 'document_verify', 'args': 'object.pk', 'famfam': 'text_signature', 'permissions': [PERMISSION_DOCUMENT_VERIFY]}
|
||||
|
||||
register_links(Document, [document_verify], menu_name='form_header')
|
||||
267
apps/django_gpg/api.py
Normal file
267
apps/django_gpg/api.py
Normal file
@@ -0,0 +1,267 @@
|
||||
import types
|
||||
from StringIO import StringIO
|
||||
from pickle import dumps
|
||||
|
||||
import gnupg
|
||||
|
||||
from django.core.files.base import File
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django_gpg.exceptions import GPGVerificationError, GPGSigningError, \
|
||||
GPGDecryptionError, KeyDeleteError, KeyGenerationError, \
|
||||
KeyFetchingError, KeyDoesNotExist
|
||||
|
||||
|
||||
KEY_TYPES = {
|
||||
'pub': _(u'Public'),
|
||||
'sec': _(u'Secret'),
|
||||
}
|
||||
|
||||
KEY_CLASS_RSA = 'RSA'
|
||||
KEY_CLASS_DSA = 'DSA'
|
||||
KEY_CLASS_ELG = 'ELG-E'
|
||||
|
||||
KEY_PRIMARY_CLASSES = (
|
||||
((KEY_CLASS_RSA), _(u'RSA')),
|
||||
((KEY_CLASS_DSA), _(u'DSA')),
|
||||
)
|
||||
|
||||
KEY_SECONDARY_CLASSES = (
|
||||
((KEY_CLASS_RSA), _(u'RSA')),
|
||||
((KEY_CLASS_ELG), _(u'Elgamal')),
|
||||
)
|
||||
|
||||
class Key(object):
|
||||
@staticmethod
|
||||
def get_key_id(fingerprint):
|
||||
return fingerprint[-16:]
|
||||
|
||||
@classmethod
|
||||
def get_all(cls, gpg, secret=False, exclude=None):
|
||||
result = []
|
||||
keys = gpg.gpg.list_keys(secret=secret)
|
||||
if exclude:
|
||||
excluded_id = exclude.key_id
|
||||
else:
|
||||
excluded_id = u''
|
||||
for key in keys:
|
||||
if not key['keyid'] in excluded_id:
|
||||
key_instance = Key(
|
||||
fingerprint=key['fingerprint'],
|
||||
uids=key['uids'],
|
||||
type=key['type'],
|
||||
data=gpg.gpg.export_keys([key['keyid']], secret=secret)
|
||||
)
|
||||
result.append(key_instance)
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get(cls, gpg, key_id, secret=False, search_keyservers=False):
|
||||
if len(key_id) > 16:
|
||||
# key_id is a fingerprint
|
||||
key_id = Key.get_key_id(key_id)
|
||||
|
||||
keys = gpg.gpg.list_keys(secret=secret)
|
||||
key = next((key for key in keys if key['keyid'] == key_id), None)
|
||||
if not key:
|
||||
if search_keyservers and secret==False:
|
||||
try:
|
||||
gpg.receive_key(key_id)
|
||||
return Key(gpg, key_id)
|
||||
except KeyFetchingError:
|
||||
raise KeyDoesNotExist
|
||||
else:
|
||||
raise KeyDoesNotExist
|
||||
|
||||
key_instance = Key(
|
||||
fingerprint=key['fingerprint'],
|
||||
uids=key['uids'],
|
||||
type=key['type'],
|
||||
data=gpg.gpg.export_keys([key['keyid']], secret=secret)
|
||||
)
|
||||
|
||||
return key_instance
|
||||
|
||||
def __init__(self, fingerprint, uids, type, data):
|
||||
self.fingerprint = fingerprint
|
||||
self.uids = uids
|
||||
self.type = type
|
||||
self.data = data
|
||||
|
||||
@property
|
||||
def key_id(self):
|
||||
return Key.get_key_id(self.fingerprint)
|
||||
|
||||
@property
|
||||
def user_ids(self):
|
||||
return u', '.join(self.uids)
|
||||
|
||||
def __str__(self):
|
||||
return '%s "%s" (%s)' % (self.key_id, self.user_ids, KEY_TYPES.get(self.type, _(u'unknown')))
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.__str__())
|
||||
|
||||
def __repr__(self):
|
||||
return self.__unicode__()
|
||||
|
||||
|
||||
class GPG(object):
|
||||
def __init__(self, binary_path=None, home=None, keyring=None, keyservers=None):
|
||||
kwargs = {}
|
||||
if binary_path:
|
||||
kwargs['gpgbinary'] = binary_path
|
||||
|
||||
if home:
|
||||
kwargs['gnupghome'] = home
|
||||
|
||||
if keyring:
|
||||
kwargs['keyring'] = keyring
|
||||
|
||||
self.keyservers = keyservers
|
||||
|
||||
self.gpg = gnupg.GPG(**kwargs)
|
||||
|
||||
def verify_w_retry(self, file_input):
|
||||
if isinstance(file_input, types.StringTypes):
|
||||
input_descriptor = open(file_input, 'rb')
|
||||
elif isinstance(file_input, types.FileType) or isinstance(file_input, File):
|
||||
input_descriptor = file_input
|
||||
elif issubclass(file_input.__class__, StringIO):
|
||||
input_descriptor = file_input
|
||||
else:
|
||||
raise ValueError('Invalid file_input argument type')
|
||||
|
||||
try:
|
||||
verify = self.verify_file(input_descriptor)
|
||||
if verify.status == 'no public key':
|
||||
# Try to fetch the public key from the keyservers
|
||||
try:
|
||||
self.receive_key(verify.key_id)
|
||||
return self.verify_w_retry(file_input)
|
||||
except KeyFetchingError:
|
||||
return verify
|
||||
else:
|
||||
return verify
|
||||
except IOError:
|
||||
return False
|
||||
|
||||
def verify_file(self, file_input):
|
||||
"""
|
||||
Verify the signature of a file.
|
||||
"""
|
||||
if isinstance(file_input, types.StringTypes):
|
||||
descriptor = open(file_input, 'rb')
|
||||
elif isinstance(file_input, types.FileType) or isinstance(file_input, File) or isinstance(file_input, StringIO):
|
||||
descriptor = file_input
|
||||
else:
|
||||
raise ValueError('Invalid file_input argument type')
|
||||
|
||||
verify = self.gpg.verify_file(descriptor)
|
||||
descriptor.close()
|
||||
|
||||
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
|
||||
else:
|
||||
raise GPGVerificationError()
|
||||
|
||||
def verify(self, data):
|
||||
# TODO: try to merge with verify_file
|
||||
verify = self.gpg.verify(data)
|
||||
|
||||
if verify:
|
||||
return verify
|
||||
else:
|
||||
raise GPGVerificationError(verify.status)
|
||||
|
||||
def sign_file(self, file_input, key=None, destination=None, key_id=None, passphrase=None, clearsign=False):
|
||||
"""
|
||||
Signs a filename, storing the signature and the original file
|
||||
in the destination filename provided (the destination file is
|
||||
overrided if it already exists), if no destination file name is
|
||||
provided the signature is returned.
|
||||
"""
|
||||
kwargs = {}
|
||||
kwargs['clearsign'] = clearsign
|
||||
|
||||
if key_id:
|
||||
kwargs['keyid'] = key_id
|
||||
|
||||
if key:
|
||||
kwargs['keyid'] = key.key_id
|
||||
|
||||
if passphrase:
|
||||
kwargs['passphrase'] = passphrase
|
||||
|
||||
if isinstance(file_input, types.StringTypes):
|
||||
input_descriptor = open(file_input, 'rb')
|
||||
elif isinstance(file_input, types.FileType) or isinstance(file_input, File):
|
||||
input_descriptor = file_input
|
||||
elif issubclass(file_input.__class__, StringIO):
|
||||
input_descriptor = file_input
|
||||
else:
|
||||
raise ValueError('Invalid file_input argument type')
|
||||
|
||||
if destination:
|
||||
output_descriptor = open(destination, 'wb')
|
||||
|
||||
signed_data = self.gpg.sign_file(input_descriptor, **kwargs)
|
||||
if not signed_data.fingerprint:
|
||||
raise GPGSigningError('Unable to sign file')
|
||||
|
||||
if destination:
|
||||
output_descriptor.write(signed_data.data)
|
||||
|
||||
input_descriptor.close()
|
||||
|
||||
if destination:
|
||||
output_descriptor.close()
|
||||
|
||||
if not destination:
|
||||
return signed_data
|
||||
|
||||
def decrypt_file(self, file_input):
|
||||
if isinstance(file_input, types.StringTypes):
|
||||
input_descriptor = open(file_input, 'rb')
|
||||
elif isinstance(file_input, types.FileType) or isinstance(file_input, File) or isinstance(file_input, StringIO):
|
||||
input_descriptor = file_input
|
||||
else:
|
||||
raise ValueError('Invalid file_input argument type')
|
||||
|
||||
result = self.gpg.decrypt_file(input_descriptor)
|
||||
input_descriptor.close()
|
||||
if not result.status:
|
||||
raise GPGDecryptionError('Unable to decrypt file')
|
||||
|
||||
return result
|
||||
|
||||
def create_key(self, *args, **kwargs):
|
||||
if kwargs.get('passphrase') == u'':
|
||||
kwargs.pop('passphrase')
|
||||
|
||||
input_data = self.gpg.gen_key_input(**kwargs)
|
||||
key = self.gpg.gen_key(input_data)
|
||||
if not key:
|
||||
raise KeyGenerationError('Unable to generate key')
|
||||
|
||||
return Key.get(self, key.fingerprint)
|
||||
|
||||
def delete_key(self, key):
|
||||
status = self.gpg.delete_keys(key.fingerprint, key.type == 'sec').status
|
||||
if status == 'Must delete secret key first':
|
||||
self.delete_key(Key.get(self, key.fingerprint, secret=True))
|
||||
self.delete_key(key)
|
||||
elif status != 'ok':
|
||||
raise KeyDeleteError('Unable to delete key')
|
||||
|
||||
def receive_key(self, key_id):
|
||||
for keyserver in self.keyservers:
|
||||
import_result = self.gpg.recv_keys(keyserver, key_id)
|
||||
if import_result:
|
||||
return Key.get(self, import_result.fingerprints[0], secret=False)
|
||||
|
||||
raise KeyFetchingError
|
||||
0
apps/django_gpg/conf/__init__.py
Normal file
0
apps/django_gpg/conf/__init__.py
Normal file
15
apps/django_gpg/conf/settings.py
Normal file
15
apps/django_gpg/conf/settings.py
Normal file
@@ -0,0 +1,15 @@
|
||||
'''
|
||||
Configuration options for the django_gpg app
|
||||
'''
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from smart_settings.api import register_settings
|
||||
|
||||
register_settings(
|
||||
namespace=u'django_gpg',
|
||||
module=u'django_gpg.conf.settings',
|
||||
settings=[
|
||||
{'name': u'KEYSERVERS', 'global_name': u'SIGNATURES_KEYSERVERS', 'default': ['keyserver.ubuntu.com'], 'description': _(u'List of keyservers to be queried for unknown keys.')},
|
||||
]
|
||||
)
|
||||
31
apps/django_gpg/exceptions.py
Normal file
31
apps/django_gpg/exceptions.py
Normal file
@@ -0,0 +1,31 @@
|
||||
class GPGException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GPGVerificationError(GPGException):
|
||||
pass
|
||||
|
||||
|
||||
class GPGSigningError(GPGException):
|
||||
pass
|
||||
|
||||
|
||||
class GPGDecryptionError(GPGException):
|
||||
pass
|
||||
|
||||
|
||||
class KeyDeleteError(GPGException):
|
||||
pass
|
||||
|
||||
|
||||
class KeyGenerationError(GPGException):
|
||||
pass
|
||||
|
||||
|
||||
class KeyFetchingError(GPGException):
|
||||
pass
|
||||
|
||||
|
||||
class KeyDoesNotExist(GPGException):
|
||||
pass
|
||||
|
||||
203
apps/django_gpg/locale/en/LC_MESSAGES/django.po
Normal file
203
apps/django_gpg/locale/en/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,203 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-11-28 05:18-0400\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: api.py:18
|
||||
msgid "Public"
|
||||
msgstr ""
|
||||
|
||||
#: api.py:19
|
||||
msgid "Secret"
|
||||
msgstr ""
|
||||
|
||||
#: api.py:27 api.py:32
|
||||
msgid "RSA"
|
||||
msgstr ""
|
||||
|
||||
#: api.py:28
|
||||
msgid "DSA"
|
||||
msgstr ""
|
||||
|
||||
#: api.py:33
|
||||
msgid "Elgamal"
|
||||
msgstr ""
|
||||
|
||||
#: api.py:89
|
||||
msgid "unknown"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:12
|
||||
msgid "Real name"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:13
|
||||
msgid "Your real name."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:17
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:19
|
||||
msgid "A comment or a note to help identify this key."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:23
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:28
|
||||
msgid "Primary key class"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:29
|
||||
msgid "The key that will be used to sign uploaded content."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:33
|
||||
msgid "Primary key size (in bytes)"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:41
|
||||
msgid "Secondary key class"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:42
|
||||
msgid "The key that will be used to encrypt uploaded content."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:46
|
||||
msgid "Secondary key size (in bytes)"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:53
|
||||
msgid "Expiration"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:54
|
||||
msgid ""
|
||||
"You can use 0 for a non expiring key, an ISO date in the form: <year>-"
|
||||
"<month>-<day> or a date difference from the current date in the forms: "
|
||||
"<days>d, <months>m, <weeks>w or <years>y."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:59
|
||||
msgid "Passphrase"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:65
|
||||
msgid "Passphrase (verification)"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:72
|
||||
msgid "Both passphrase fields entries must match."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:80
|
||||
msgid "Key"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:81
|
||||
msgid "Key to be published, only the public part of the key will be sent."
|
||||
msgstr ""
|
||||
|
||||
#: tasks.py:27
|
||||
#, python-format
|
||||
msgid "Key pair: %s, created successfully."
|
||||
msgstr ""
|
||||
|
||||
#: tasks.py:34
|
||||
#, python-format
|
||||
msgid "Key creation error; %s"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:27
|
||||
msgid "Private key list"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:30
|
||||
msgid "Public key list"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:54
|
||||
msgid "Key pair queued for creation, refresh this page to check results."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:64
|
||||
msgid "Create a new key"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:65
|
||||
msgid ""
|
||||
"The key creation process can take quite some time to complete, please be "
|
||||
"patient."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:75
|
||||
#, python-format
|
||||
msgid "Key: %s, deleted successfully."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:82
|
||||
msgid "Delete key"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:83
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Are you sure you wish to delete key:%s? If you try to delete a public key "
|
||||
"that is part of a public/private pair the private key will be deleted as "
|
||||
"well."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:95
|
||||
#, python-format
|
||||
msgid "Key publish request for key: %s, has been sent"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:98
|
||||
msgid "Unable to send key publish call"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:105
|
||||
msgid "Publish a key to the OpenRelay network"
|
||||
msgstr ""
|
||||
|
||||
#: templates/key_list.html:10
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#: templates/key_list.html:11
|
||||
msgid "User IDs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/key_list.html:12
|
||||
msgid "Fingerprint"
|
||||
msgstr ""
|
||||
|
||||
#: templates/key_list.html:13
|
||||
msgid "Links"
|
||||
msgstr ""
|
||||
|
||||
#: templates/key_list.html:22
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: templates/key_list.html:26
|
||||
msgid "There are no keys available."
|
||||
msgstr ""
|
||||
BIN
apps/django_gpg/locale/es/LC_MESSAGES/django.mo
Normal file
BIN
apps/django_gpg/locale/es/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
217
apps/django_gpg/locale/es/LC_MESSAGES/django.po
Normal file
217
apps/django_gpg/locale/es/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,217 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
#
|
||||
# Translators:
|
||||
# Roberto Rosario <roberto.rosario.gonzalez@gmail.com>, 2011.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: OpenRelay\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-11-28 05:18-0400\n"
|
||||
"PO-Revision-Date: 2011-11-28 09:25+0000\n"
|
||||
"Last-Translator: rosarior <roberto.rosario.gonzalez@gmail.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: es\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
|
||||
#: api.py:18
|
||||
msgid "Public"
|
||||
msgstr "Pública"
|
||||
|
||||
#: api.py:19
|
||||
msgid "Secret"
|
||||
msgstr "Secreta"
|
||||
|
||||
#: api.py:27 api.py:32
|
||||
msgid "RSA"
|
||||
msgstr "RSA"
|
||||
|
||||
#: api.py:28
|
||||
msgid "DSA"
|
||||
msgstr "DSA"
|
||||
|
||||
#: api.py:33
|
||||
msgid "Elgamal"
|
||||
msgstr "Elgamal"
|
||||
|
||||
#: api.py:89
|
||||
msgid "unknown"
|
||||
msgstr "desconocido"
|
||||
|
||||
#: forms.py:12
|
||||
msgid "Real name"
|
||||
msgstr "Nombre real"
|
||||
|
||||
#: forms.py:13
|
||||
msgid "Your real name."
|
||||
msgstr "Su nombre real."
|
||||
|
||||
#: forms.py:17
|
||||
msgid "Comment"
|
||||
msgstr "Comentario"
|
||||
|
||||
#: forms.py:19
|
||||
msgid "A comment or a note to help identify this key."
|
||||
msgstr "Un comentario o una nota para ayudar a identificar esta llave."
|
||||
|
||||
#: forms.py:23
|
||||
msgid "Email"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: forms.py:28
|
||||
msgid "Primary key class"
|
||||
msgstr "Clase de llave primaria"
|
||||
|
||||
#: forms.py:29
|
||||
msgid "The key that will be used to sign uploaded content."
|
||||
msgstr "La llave que se utilizara para firmar el contenido subido."
|
||||
|
||||
#: forms.py:33
|
||||
msgid "Primary key size (in bytes)"
|
||||
msgstr "Tamaño de la llave principal (en bytes)"
|
||||
|
||||
#: forms.py:41
|
||||
msgid "Secondary key class"
|
||||
msgstr "Clase de llave secundaria"
|
||||
|
||||
#: forms.py:42
|
||||
msgid "The key that will be used to encrypt uploaded content."
|
||||
msgstr "La llave que se utilizara para cifrar el contenido subido."
|
||||
|
||||
#: forms.py:46
|
||||
msgid "Secondary key size (in bytes)"
|
||||
msgstr "Tamaño de la llave secundaria (en bytes)"
|
||||
|
||||
#: forms.py:53
|
||||
msgid "Expiration"
|
||||
msgstr "Expiración"
|
||||
|
||||
#: forms.py:54
|
||||
msgid ""
|
||||
"You can use 0 for a non expiring key, an ISO date in the form: "
|
||||
"<year>-<month>-<day> or a date difference from the current date in the "
|
||||
"forms: <days>d, <months>m, <weeks>w or <years>y."
|
||||
msgstr ""
|
||||
"Usted puede utilizar 0 para llaves que no expiran, una fecha ISO en la "
|
||||
"forma: <año>-<mes>-<dia> o una diferencia de fecha con respecto a la fecha "
|
||||
"actual en las formas: <dias>d, <meses>m, <semanas>w o <años>y."
|
||||
|
||||
#: forms.py:59
|
||||
msgid "Passphrase"
|
||||
msgstr "Frase de contraseña"
|
||||
|
||||
#: forms.py:65
|
||||
msgid "Passphrase (verification)"
|
||||
msgstr "Contraseña (verificación)"
|
||||
|
||||
#: forms.py:72
|
||||
msgid "Both passphrase fields entries must match."
|
||||
msgstr "Las entradas de los campos de contraseña deben coincidir."
|
||||
|
||||
#: forms.py:80
|
||||
msgid "Key"
|
||||
msgstr "Llave"
|
||||
|
||||
#: forms.py:81
|
||||
msgid "Key to be published, only the public part of the key will be sent."
|
||||
msgstr ""
|
||||
"La llave para ser publicada, sólo la parte pública de la llave será enviada."
|
||||
|
||||
#: tasks.py:27
|
||||
#, python-format
|
||||
msgid "Key pair: %s, created successfully."
|
||||
msgstr "Par de llaves: %s, creado correctamente."
|
||||
|
||||
#: tasks.py:34
|
||||
#, python-format
|
||||
msgid "Key creation error; %s"
|
||||
msgstr "Error de creación de llave; %s"
|
||||
|
||||
#: views.py:27
|
||||
msgid "Private key list"
|
||||
msgstr "Lista de llaves privadas"
|
||||
|
||||
#: views.py:30
|
||||
msgid "Public key list"
|
||||
msgstr "Lista de llaves públicas"
|
||||
|
||||
#: views.py:54
|
||||
msgid "Key pair queued for creation, refresh this page to check results."
|
||||
msgstr ""
|
||||
"Par de llaves en lista de creación, vuelva a cargar esta página "
|
||||
"periodicamente para comprobar los resultados."
|
||||
|
||||
#: views.py:64
|
||||
msgid "Create a new key"
|
||||
msgstr "Crear una llave nueva"
|
||||
|
||||
#: views.py:65
|
||||
msgid ""
|
||||
"The key creation process can take quite some time to complete, please be "
|
||||
"patient."
|
||||
msgstr ""
|
||||
"El proceso de creación de la llaves puede tomar bastante tiempo en "
|
||||
"completar, por favor, sea paciente."
|
||||
|
||||
#: views.py:75
|
||||
#, python-format
|
||||
msgid "Key: %s, deleted successfully."
|
||||
msgstr "Llave: %s, eliminada exitosamente."
|
||||
|
||||
#: views.py:82
|
||||
msgid "Delete key"
|
||||
msgstr "Eliminar la llave"
|
||||
|
||||
#: views.py:83
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Are you sure you wish to delete key:%s? If you try to delete a public key "
|
||||
"that is part of a public/private pair the private key will be deleted as "
|
||||
"well."
|
||||
msgstr ""
|
||||
"¿Está seguro que desea eliminar la llave: %s? Si intenta eliminar una llave"
|
||||
" pública que forma parte de una pareja pública / privada de la llave privada"
|
||||
" se eliminarán también."
|
||||
|
||||
#: views.py:95
|
||||
#, python-format
|
||||
msgid "Key publish request for key: %s, has been sent"
|
||||
msgstr "Solicitud publicación de llave: %s, ha sido enviada"
|
||||
|
||||
#: views.py:98
|
||||
msgid "Unable to send key publish call"
|
||||
msgstr "No se puede enviar llave publica"
|
||||
|
||||
#: views.py:105
|
||||
msgid "Publish a key to the OpenRelay network"
|
||||
msgstr "Publicar una llave a la red OpenRelay"
|
||||
|
||||
#: templates/key_list.html:10
|
||||
msgid "ID"
|
||||
msgstr "Identificador"
|
||||
|
||||
#: templates/key_list.html:11
|
||||
msgid "User IDs"
|
||||
msgstr "ID de usuarios"
|
||||
|
||||
#: templates/key_list.html:12
|
||||
msgid "Fingerprint"
|
||||
msgstr "Huella digital"
|
||||
|
||||
#: templates/key_list.html:13
|
||||
msgid "Links"
|
||||
msgstr "Enlaces"
|
||||
|
||||
#: templates/key_list.html:22
|
||||
msgid "Delete"
|
||||
msgstr "Eliminar"
|
||||
|
||||
#: templates/key_list.html:26
|
||||
msgid "There are no keys available."
|
||||
msgstr "No hay llaves disponibles."
|
||||
|
||||
|
||||
1
apps/django_gpg/models.py
Normal file
1
apps/django_gpg/models.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
4
apps/django_gpg/runtime.py
Normal file
4
apps/django_gpg/runtime.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from django_gpg.api import GPG
|
||||
from django_gpg.conf.settings import KEYSERVERS
|
||||
|
||||
gpg = GPG(keyservers=KEYSERVERS)
|
||||
BIN
apps/django_gpg/static/images/icons/cross.png
Normal file
BIN
apps/django_gpg/static/images/icons/cross.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
apps/django_gpg/static/images/icons/document_signature.png
Normal file
BIN
apps/django_gpg/static/images/icons/document_signature.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
apps/django_gpg/static/images/icons/user_silhouette.png
Normal file
BIN
apps/django_gpg/static/images/icons/user_silhouette.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
8
apps/django_gpg/urls.py
Normal file
8
apps/django_gpg/urls.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
urlpatterns = patterns('django_gpg.views',
|
||||
url(r'^delete/(?P<fingerprint>.+)/(?P<key_type>\w+)/$', 'key_delete', (), 'key_delete'),
|
||||
url(r'^list/private/$', 'key_list', {'secret': True}, 'key_private_list'),
|
||||
url(r'^list/public/$', 'key_list', {'secret': False}, 'key_public_list'),
|
||||
url(r'^verify/(?P<document_pk>\d+)/$', 'document_verify', (), 'document_verify'),
|
||||
)
|
||||
113
apps/django_gpg/views.py
Normal file
113
apps/django_gpg/views.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render_to_response, get_object_or_404
|
||||
from django.template import RequestContext
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.conf import settings
|
||||
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.runtime import gpg
|
||||
from django_gpg.exceptions import GPGVerificationError
|
||||
from django_gpg import PERMISSION_DOCUMENT_VERIFY
|
||||
|
||||
|
||||
def key_list(request, secret=True):
|
||||
if secret:
|
||||
object_list = Key.get_all(gpg, secret=True)
|
||||
title = _(u'Private key list')
|
||||
else:
|
||||
object_list = Key.get_all(gpg)
|
||||
title = _(u'Public key list')
|
||||
|
||||
return render_to_response('key_list.html', {
|
||||
'object_list': object_list,
|
||||
'title': title,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
|
||||
def key_delete(request, fingerprint, key_type):
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
secret = key_type == 'sec'
|
||||
key = Key.get(gpg, fingerprint, secret=secret)
|
||||
gpg.delete_key(key)
|
||||
messages.success(request, _(u'Key: %s, deleted successfully.') % fingerprint)
|
||||
return HttpResponseRedirect(reverse('home_view'))
|
||||
except Exception, msg:
|
||||
messages.error(request, msg)
|
||||
return HttpResponseRedirect(reverse('home_view'))
|
||||
|
||||
return render_to_response('generic_confirm.html', {
|
||||
'title': _(u'Delete key'),
|
||||
'message': _(u'Are you sure you wish to delete key:%s? If you try to delete a public key that is part of a public/private pair the private key will be deleted as well.') % Key.get(gpg, fingerprint)
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
|
||||
def document_verify(request, document_pk):
|
||||
check_permissions(request.user, [PERMISSION_DOCUMENT_VERIFY])
|
||||
document = get_object_or_404(Document, pk=document_pk)
|
||||
|
||||
RecentDocument.objects.add_document_for_user(request.user, document)
|
||||
try:
|
||||
signature = gpg.verify_w_retry(document.open())
|
||||
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))
|
||||
|
||||
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 signature:
|
||||
paragraphs.extend(
|
||||
[
|
||||
_(u'Signature ID: %s') % signature.signature_id,
|
||||
_(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'')),
|
||||
]
|
||||
)
|
||||
|
||||
return render_to_response('generic_template.html', {
|
||||
'title': _(u'signature properties for: %s') % document,
|
||||
'object': document,
|
||||
'document': document,
|
||||
'paragraphs': paragraphs,
|
||||
}, context_instance=RequestContext(request))
|
||||
@@ -1155,6 +1155,8 @@ def document_version_list(request, document_pk):
|
||||
check_permissions(request.user, [PERMISSION_DOCUMENT_VIEW])
|
||||
document = get_object_or_404(Document, pk=document_pk)
|
||||
|
||||
RecentDocument.objects.add_document_for_user(request.user, document)
|
||||
|
||||
context = {
|
||||
'object_list': document.versions.order_by('-timestamp'),
|
||||
'title': _(u'versions for document: %s') % document,
|
||||
|
||||
@@ -20,3 +20,4 @@ django-compressor==1.1
|
||||
-e git://github.com/rosarior/django-sendfile.git#egg=django-sendfile
|
||||
djangorestframework==0.2.3
|
||||
South==0.7.3
|
||||
python-gnupg==0.2.8
|
||||
|
||||
@@ -17,3 +17,4 @@ django-compressor==1.1
|
||||
-e git://github.com/rosarior/django-sendfile.git#egg=django-sendfile
|
||||
djangorestframework==0.2.3
|
||||
South==0.7.3
|
||||
python-gnupg==0.2.8
|
||||
|
||||
@@ -133,6 +133,7 @@ INSTALLED_APPS = (
|
||||
'lock_manager',
|
||||
'web_theme',
|
||||
'common',
|
||||
'django_gpg',
|
||||
'pagination',
|
||||
'dynamic_search',
|
||||
'filetransfers',
|
||||
|
||||
Reference in New Issue
Block a user