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])
|
check_permissions(request.user, [PERMISSION_DOCUMENT_VIEW])
|
||||||
document = get_object_or_404(Document, pk=document_pk)
|
document = get_object_or_404(Document, pk=document_pk)
|
||||||
|
|
||||||
|
RecentDocument.objects.add_document_for_user(request.user, document)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'object_list': document.versions.order_by('-timestamp'),
|
'object_list': document.versions.order_by('-timestamp'),
|
||||||
'title': _(u'versions for document: %s') % document,
|
'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
|
-e git://github.com/rosarior/django-sendfile.git#egg=django-sendfile
|
||||||
djangorestframework==0.2.3
|
djangorestframework==0.2.3
|
||||||
South==0.7.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
|
-e git://github.com/rosarior/django-sendfile.git#egg=django-sendfile
|
||||||
djangorestframework==0.2.3
|
djangorestframework==0.2.3
|
||||||
South==0.7.3
|
South==0.7.3
|
||||||
|
python-gnupg==0.2.8
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ INSTALLED_APPS = (
|
|||||||
'lock_manager',
|
'lock_manager',
|
||||||
'web_theme',
|
'web_theme',
|
||||||
'common',
|
'common',
|
||||||
|
'django_gpg',
|
||||||
'pagination',
|
'pagination',
|
||||||
'dynamic_search',
|
'dynamic_search',
|
||||||
'filetransfers',
|
'filetransfers',
|
||||||
|
|||||||
1
urls.py
1
urls.py
@@ -28,6 +28,7 @@ urlpatterns = patterns('',
|
|||||||
(r'^project_setup/', include('project_setup.urls')),
|
(r'^project_setup/', include('project_setup.urls')),
|
||||||
(r'^project_tools/', include('project_tools.urls')),
|
(r'^project_tools/', include('project_tools.urls')),
|
||||||
(r'^api/', include('rest_api.urls')),
|
(r'^api/', include('rest_api.urls')),
|
||||||
|
(r'^signatures/', include('django_gpg.urls')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user