Place KeyManage on a seprate module. Convert views to use the new Key model. Add KeyStub class and use it to return query results. Add Key detail link and view. Remove the setting for multiple keyservers.

This commit is contained in:
Roberto Rosario
2016-03-23 00:35:32 -04:00
parent 189cda437f
commit 2748d5959f
12 changed files with 259 additions and 176 deletions

View File

@@ -1,18 +1,24 @@
from __future__ import unicode_literals
from __future__ import absolute_import, unicode_literals
from datetime import datetime
from django.utils.translation import ugettext_lazy as _
from common import MayanAppConfig, menu_object, menu_setup, menu_sidebar
from acls import ModelPermission
from acls.links import link_acl_list
from acls.permissions import permission_acl_edit, permission_acl_view
from common import (
MayanAppConfig, menu_facet, menu_object, menu_setup, menu_sidebar
)
from common.classes import Package
from navigation import SourceColumn
from .api import Key, KeyStub
from .classes import KeyStub
from .links import (
link_key_delete, link_key_query, link_key_receive, link_key_setup,
link_public_keys
link_key_delete, link_key_detail, link_key_query, link_key_receive,
link_key_setup, link_private_keys, link_public_keys
)
from .permissions import permission_key_delete, permission_key_view
class DjangoGPGApp(MayanAppConfig):
@@ -24,6 +30,15 @@ class DjangoGPGApp(MayanAppConfig):
def ready(self):
super(DjangoGPGApp, self).ready()
Key = self.get_model('Key')
ModelPermission.register(
model=Key, permissions=(
permission_acl_edit, permission_acl_view,
permission_key_delete, permission_key_view
)
)
Package(label='python-gnupg', license_text='''
Copyright (c) 2008-2014 by Vinay Sajip.
All rights reserved.
@@ -52,11 +67,8 @@ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''')
SourceColumn(source=Key, label=_('ID'), attribute='key_id')
SourceColumn(
source=Key, label=_('Owner'),
func=lambda context: ', '.join(context['object'].uids)
)
SourceColumn(source=Key, label=_('Key ID'), attribute='key_id')
SourceColumn(source=Key, label=_('User ID'), attribute='user_id')
SourceColumn(
source=KeyStub, label=_('ID'),
@@ -75,17 +87,30 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
)
SourceColumn(source=KeyStub, label=_('Length'), attribute='length')
SourceColumn(
source=KeyStub, label=_('Identities'),
source=KeyStub, label=_('User ID'),
func=lambda context: ', '.join(context['object'].uids)
)
menu_object.bind_links(links=(link_key_delete,), sources=(Key,))
menu_object.bind_links(links=(link_key_detail,), sources=(Key,))
menu_object.bind_links(links=(link_key_receive,), sources=(KeyStub,))
menu_object.bind_links(
links=(link_acl_list, link_key_delete,), sources=(Key,)
)
menu_setup.bind_links(links=(link_key_setup,))
menu_sidebar.bind_links(
links=(link_public_keys, link_key_query),
menu_facet.bind_links(
links=(link_private_keys, link_public_keys),
sources=(
'django_gpg:key_delete', 'django_gpg:key_public_list',
'django_gpg:key_query', 'django_gpg:key_query_results',
'django_gpg:key_public_list', 'django_gpg:key_private_list',
'django_gpg:key_query', 'django_gpg:key_query_results', Key,
KeyStub
)
)
menu_sidebar.bind_links(
links=(link_key_query,),
sources=(
'django_gpg:key_public_list', 'django_gpg:key_private_list',
'django_gpg:key_query', 'django_gpg:key_query_results', Key,
KeyStub
)
)

View File

@@ -0,0 +1,11 @@
from __future__ import absolute_import, unicode_literals
class KeyStub(object):
def __init__(self, raw):
self.key_id = raw['keyid']
self.key_type = raw['type']
self.date = raw['date']
self.expires = raw['expires']
self.length = raw['length']
self.uids = raw['uids']

View File

@@ -1,8 +1,46 @@
from __future__ import unicode_literals
from django import forms
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _
from common.forms import DetailForm
from .models import Key
class KeyDetailForm(DetailForm):
def __init__(self, *args, **kwargs):
instance = kwargs['instance']
extra_fields = (
{'label': _('Key ID'), 'field': 'key_id'},
{
'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': _('key_type'), 'field': 'key_type'},
)
kwargs['extra_fields'] = extra_fields
super(KeyDetailForm, self).__init__(*args, **kwargs)
class Meta:
fields = ()
model = Key
class KeySearchForm(forms.Form):
term = forms.CharField(

View File

@@ -10,18 +10,21 @@ from .permissions import (
)
link_private_keys = Link(
icon='fa fa-key', permissions=(permission_key_view,),
text=_('Private keys'), view='django_gpg:key_private_list'
permissions=(permission_key_view,), text=_('Private keys'),
view='django_gpg:key_private_list'
)
link_public_keys = Link(
icon='fa fa-key', permissions=(permission_key_view,),
text=_('Public keys'), view='django_gpg:key_public_list'
permissions=(permission_key_view,), text=_('Public keys'),
view='django_gpg:key_public_list'
)
link_key_delete = Link(
permissions=(permission_key_delete,), tags='dangerous', text=_('Delete'),
view='django_gpg:key_delete', args=('object.fingerprint', 'object.type',)
view='django_gpg:key_delete', args=('resolved_object.pk',)
)
link_key_detail = Link(
permissions=(permission_key_view,), text=_('Details'),
view='django_gpg:key_detail', args=('resolved_object.pk',)
)
link_key_query = Link(
permissions=(permission_keyserver_query,), text=_('Query keyservers'),
view='django_gpg:key_query'

View File

@@ -61,3 +61,8 @@ SIGNATURE_STATES = {
'text': _('Document is signed with a valid signature.'),
},
}
ERROR_MSG_NEED_PASSPHRASE = 'NEED_PASSPHRASE'
ERROR_MSG_BAD_PASSPHRASE = 'BAD_PASSPHRASE'
ERROR_MSG_GOOD_PASSPHRASE = 'GOOD_PASSPHRASE'
OUTPUT_MESSAGE_CONTAINS_PRIVATE_KEY = 'Contains private key'

View File

@@ -0,0 +1,59 @@
from __future__ import absolute_import, unicode_literals
import logging
import os
import shutil
import tempfile
import gnupg
from django.db import models
from .classes import KeyStub
from .literals import KEY_TYPE_PUBLIC, KEY_TYPE_SECRET
from .settings import setting_gpg_path, setting_keyserver
logger = logging.getLogger(__name__)
class KeyManager(models.Manager):
def receive_key(self, key_id):
temporary_directory = tempfile.mkdtemp()
os.chmod(temporary_directory, 0x1C0)
gpg = gnupg.GPG(
gnupghome=temporary_directory, gpgbinary=setting_gpg_path.value
)
import_results = gpg.recv_keys(setting_keyserver.value, key_id)
key_data = gpg.export_keys(import_results.fingerprints[0])
shutil.rmtree(temporary_directory)
return self.create(key_data=key_data)
def search(self, query):
temporary_directory = tempfile.mkdtemp()
gpg = gnupg.GPG(
gnupghome=temporary_directory, gpgbinary=setting_gpg_path.value
)
key_data_list = gpg.search_keys(
query=query, keyserver=setting_keyserver.value
)
shutil.rmtree(temporary_directory)
result = []
for key_data in key_data_list:
result.append(KeyStub(raw=key_data))
return result
def public_keys(self):
return self.filter(key_type=KEY_TYPE_PUBLIC)
def private_keys(self):
return self.filter(key_type=KEY_TYPE_SECRET)

View File

@@ -9,18 +9,20 @@ import tempfile
import gnupg
from django.core.exceptions import ValidationError
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 .literals import KEY_TYPE_CHOICES, KEY_TYPE_SECRET
from .exceptions import NeedPassphrase, PassphraseError
from .settings import setting_gpg_path, setting_keyserver
from .literals import (
ERROR_MSG_NEED_PASSPHRASE, ERROR_MSG_BAD_PASSPHRASE,
ERROR_MSG_GOOD_PASSPHRASE, KEY_TYPE_CHOICES, KEY_TYPE_SECRET,
OUTPUT_MESSAGE_CONTAINS_PRIVATE_KEY
)
from .managers import KeyManager
from .settings import setting_gpg_path
ERROR_MSG_NEED_PASSPHRASE = 'NEED_PASSPHRASE'
ERROR_MSG_BAD_PASSPHRASE = 'BAD_PASSPHRASE'
ERROR_MSG_GOOD_PASSPHRASE = 'GOOD_PASSPHRASE'
OUTPUT_MESSAGE_CONTAINS_PRIVATE_KEY = 'Contains private key'
logger = logging.getLogger(__name__)
@@ -39,45 +41,6 @@ def gpg_command(function):
return result
class KeyManager(models.Manager):
def receive_key(self, key_id):
temporary_directory = tempfile.mkdtemp()
os.chmod(temporary_directory, 0x1C0)
gpg = gnupg.GPG(
gnupghome=temporary_directory, gpgbinary=setting_gpg_path.value
)
import_results = gpg.recv_keys(setting_keyserver.value, key_id)
key_data = gpg.export_keys(import_results.fingerprints[0])
shutil.rmtree(temporary_directory)
return self.create(data=key_data)
def search(self, query):
temporary_directory = tempfile.mkdtemp()
gpg = gnupg.GPG(
gnupghome=temporary_directory, gpgbinary=setting_gpg_path.value
)
result = gpg.search_keys(
query=query, keyserver=setting_keyserver.value
)
shutil.rmtree(temporary_directory)
return result
def public_keys(self):
return self.filter(key_type='pub')
def private_keys(self):
return self.filter(key_type='')
@python_2_unicode_compatible
class Key(models.Model):
key_data = models.TextField(verbose_name=_('Key data'))
@@ -122,6 +85,9 @@ class Key(models.Model):
if not import_results.count:
raise ValidationError('Invalid key data')
def get_absolute_url(self):
return reverse('django_gpg:key_detail', args=(self.pk,))
def save(self, *args, **kwargs):
temporary_directory = tempfile.mkdtemp()

View File

@@ -6,15 +6,15 @@ from permissions import PermissionNamespace
namespace = PermissionNamespace('django_gpg', _('Key management'))
permission_key_view = namespace.add_permission(
name='key_view', label=_('View keys')
)
permission_key_delete = namespace.add_permission(
name='key_delete', label=_('Delete keys')
)
permission_keyserver_query = namespace.add_permission(
name='keyserver_query', label=_('Query keyservers')
)
permission_key_receive = namespace.add_permission(
name='key_receive', label=_('Import keys from keyservers')
)
permission_key_view = namespace.add_permission(
name='key_view', label=_('View keys')
)
permission_keyserver_query = namespace.add_permission(
name='keyserver_query', label=_('Query keyservers')
)

View File

@@ -1,7 +0,0 @@
from .api import GPG
from .settings import setting_gpg_home, setting_gpg_path, setting_keyservers
gpg = GPG(
binary_path=setting_gpg_path.value, home=setting_gpg_home.value,
keyservers=setting_keyservers.value
)

View File

@@ -24,7 +24,3 @@ setting_keyserver = namespace.add_setting(
global_name='SIGNATURES_KEYSERVER', default='pool.sks-keyservers.net',
help_text=_('Keyserver used to query for keys.')
)
setting_keyservers = namespace.add_setting(
global_name='SIGNATURES_KEYSERVERS', default=['pool.sks-keyservers.net'],
help_text=_('List of keyservers to be queried for unknown keys.')
)

View File

@@ -3,14 +3,17 @@ from __future__ import unicode_literals
from django.conf.urls import patterns, url
from .views import (
KeyQueryView, KeyQueryResultView, PrivateKeyListView, PublicKeyListView
KeyDeleteView, KeyDetailView, KeyQueryView, KeyQueryResultView, KeyReceive,
PrivateKeyListView, PublicKeyListView
)
urlpatterns = patterns(
'django_gpg.views',
url(
r'^delete/(?P<fingerprint>.+)/(?P<key_type>\w+)/$', 'key_delete',
name='key_delete'
r'^(?P<pk>\d+)/$', KeyDetailView.as_view(), name='key_detail'
),
url(
r'^delete/(?P<pk>\d+)/$', KeyDeleteView.as_view(), name='key_delete'
),
url(
r'^list/private/$', PrivateKeyListView.as_view(),
@@ -24,5 +27,7 @@ urlpatterns = patterns(
r'^query/results/$', KeyQueryResultView.as_view(),
name='key_query_results'
),
url(r'^receive/(?P<key_id>.+)/$', 'key_receive', name='key_receive'),
url(
r'^receive/(?P<key_id>.+)/$', KeyReceive.as_view(), name='key_receive'
),
)

View File

@@ -2,117 +2,77 @@ from __future__ import absolute_import, unicode_literals
import logging
from django.conf import settings
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import redirect, render_to_response
from django.template import RequestContext
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.translation import ugettext_lazy as _
from common.generics import SimpleView, SingleObjectListView
from permissions import Permission
from common.generics import (
ConfirmView, SimpleView, SingleObjectDeleteView, SingleObjectDetailView,
SingleObjectListView
)
from .api import Key
from .forms import KeySearchForm
from .forms import KeyDetailForm, KeySearchForm
from .models import Key
from .permissions import (
permission_key_delete, permission_key_receive, permission_key_view,
permission_keyserver_query
)
from .runtime import gpg
logger = logging.getLogger(__name__)
def key_receive(request, key_id):
Permission.check_permissions(request.user, (permission_key_receive,))
previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
if request.method == 'POST':
try:
gpg.receive_key(key_id=key_id)
except Exception as exception:
messages.error(
request,
_('Unable to import key: %(key_id)s; %(error)s') %
{
'key_id': key_id,
'error': exception,
}
)
return HttpResponseRedirect(previous)
else:
messages.success(
request,
_('Successfully received key: %(key_id)s') %
{
'key_id': key_id,
}
)
return redirect('django_gpg:key_public_list')
return render_to_response('appearance/generic_confirm.html', {
'message': _('Import key ID: %s?') % key_id,
'previous': previous,
'title': _('Import key'),
}, context_instance=RequestContext(request))
class PublicKeyListView(SingleObjectListView):
view_permission = permission_key_view
class KeyDeleteView(SingleObjectDeleteView):
model = Key
object_permission = permission_key_delete
def get_extra_context(self):
return {
'hide_object': True,
'title': self.get_title()
}
def get_queryset(self):
return Key.get_all(gpg)
def get_title(self):
return _('Public keys')
class PrivateKeyListView(PublicKeyListView):
def get_title(self):
return _('Private keys')
def get_queryset(self):
return Key.get_all(gpg, secret=True)
def key_delete(request, fingerprint, key_type):
Permission.check_permissions(request.user, (permission_key_delete,))
secret = key_type == 'sec'
key = Key.get(gpg, fingerprint, secret=secret)
post_action_redirect = redirect('django_gpg:key_public_list')
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:
gpg.delete_key(key)
messages.success(request, _('Key: %s, deleted successfully.') % fingerprint)
return HttpResponseRedirect(next)
except Exception as exception:
messages.error(request, exception)
return HttpResponseRedirect(previous)
return render_to_response('appearance/generic_confirm.html', {
'title': _('Delete key'),
'delete_view': True,
'message': _(
'Delete key %s? If you delete a public key that is part of a '
'public/private pair the private key will be deleted as well.'
) % key,
'next': next,
'previous': previous,
}, context_instance=RequestContext(request))
) % self.get_object(),
}
class KeyDetailView(SingleObjectDetailView):
form_class = KeyDetailForm
model = Key
object_permission = permission_key_view
def get_extra_context(self):
return {
'title': _('Details for key: %s') % self.get_object(),
}
class KeyReceive(ConfirmView):
post_action_redirect = reverse_lazy('django_gpg:key_public_list')
view_permission = permission_key_receive
def get_extra_context(self):
return {
'message': _('Import key ID: %s?') % self.kwargs['key_id'],
'title': _('Import key'),
}
def view_action(self):
try:
Key.objects.receive_key(key_id=self.kwargs['key_id'])
except Exception as exception:
messages.error(
self.request,
_('Unable to import key: %(key_id)s; %(error)s') % {
'key_id': self.kwargs['key_id'],
'error': exception,
}
)
else:
messages.success(
self.request, _('Successfully received key: %(key_id)s') % {
'key_id': self.kwargs['key_id'],
}
)
class KeyQueryView(SimpleView):
@@ -149,6 +109,28 @@ class KeyQueryResultView(SingleObjectListView):
def get_queryset(self):
term = self.request.GET.get('term')
if term:
return gpg.query(term)
return Key.objects.search(query=term)
else:
return ()
class PublicKeyListView(SingleObjectListView):
object_permission = permission_key_view
queryset = Key.objects.public_keys()
def get_extra_context(self):
return {
'hide_object': True,
'title': _('Public keys')
}
class PrivateKeyListView(SingleObjectListView):
object_permission = permission_key_view
queryset = Key.objects.private_keys()
def get_extra_context(self):
return {
'hide_object': True,
'title': _('Private keys')
}