From 2748d5959fa17c90542c60acb740cb6002ec1486 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Wed, 23 Mar 2016 00:35:32 -0400 Subject: [PATCH] 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. --- mayan/apps/django_gpg/apps.py | 57 +++++++--- mayan/apps/django_gpg/classes.py | 11 ++ mayan/apps/django_gpg/forms.py | 38 +++++++ mayan/apps/django_gpg/links.py | 15 ++- mayan/apps/django_gpg/literals.py | 5 + mayan/apps/django_gpg/managers.py | 59 ++++++++++ mayan/apps/django_gpg/models.py | 56 ++-------- mayan/apps/django_gpg/permissions.py | 12 +- mayan/apps/django_gpg/runtime.py | 7 -- mayan/apps/django_gpg/settings.py | 4 - mayan/apps/django_gpg/urls.py | 13 ++- mayan/apps/django_gpg/views.py | 158 ++++++++++++--------------- 12 files changed, 259 insertions(+), 176 deletions(-) create mode 100644 mayan/apps/django_gpg/classes.py create mode 100644 mayan/apps/django_gpg/managers.py delete mode 100644 mayan/apps/django_gpg/runtime.py diff --git a/mayan/apps/django_gpg/apps.py b/mayan/apps/django_gpg/apps.py index b972569438..1062fbf3a4 100644 --- a/mayan/apps/django_gpg/apps.py +++ b/mayan/apps/django_gpg/apps.py @@ -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 ) ) diff --git a/mayan/apps/django_gpg/classes.py b/mayan/apps/django_gpg/classes.py new file mode 100644 index 0000000000..6d72870e38 --- /dev/null +++ b/mayan/apps/django_gpg/classes.py @@ -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'] diff --git a/mayan/apps/django_gpg/forms.py b/mayan/apps/django_gpg/forms.py index c58b0eb02f..ecc934db39 100644 --- a/mayan/apps/django_gpg/forms.py +++ b/mayan/apps/django_gpg/forms.py @@ -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( diff --git a/mayan/apps/django_gpg/links.py b/mayan/apps/django_gpg/links.py index 2f71491674..3a6cda3ee0 100644 --- a/mayan/apps/django_gpg/links.py +++ b/mayan/apps/django_gpg/links.py @@ -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' diff --git a/mayan/apps/django_gpg/literals.py b/mayan/apps/django_gpg/literals.py index 2a8b14997a..6af8911ce9 100644 --- a/mayan/apps/django_gpg/literals.py +++ b/mayan/apps/django_gpg/literals.py @@ -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' diff --git a/mayan/apps/django_gpg/managers.py b/mayan/apps/django_gpg/managers.py new file mode 100644 index 0000000000..20b9074ab7 --- /dev/null +++ b/mayan/apps/django_gpg/managers.py @@ -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) diff --git a/mayan/apps/django_gpg/models.py b/mayan/apps/django_gpg/models.py index 14bf97b1e9..a5b61afc32 100644 --- a/mayan/apps/django_gpg/models.py +++ b/mayan/apps/django_gpg/models.py @@ -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() diff --git a/mayan/apps/django_gpg/permissions.py b/mayan/apps/django_gpg/permissions.py index dc5536b2ca..fc984d3567 100644 --- a/mayan/apps/django_gpg/permissions.py +++ b/mayan/apps/django_gpg/permissions.py @@ -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') +) diff --git a/mayan/apps/django_gpg/runtime.py b/mayan/apps/django_gpg/runtime.py deleted file mode 100644 index af8bfe4c42..0000000000 --- a/mayan/apps/django_gpg/runtime.py +++ /dev/null @@ -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 -) diff --git a/mayan/apps/django_gpg/settings.py b/mayan/apps/django_gpg/settings.py index c7d3da34f1..e8cb60c1d7 100644 --- a/mayan/apps/django_gpg/settings.py +++ b/mayan/apps/django_gpg/settings.py @@ -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.') -) diff --git a/mayan/apps/django_gpg/urls.py b/mayan/apps/django_gpg/urls.py index aae4d12c9e..47126ee413 100644 --- a/mayan/apps/django_gpg/urls.py +++ b/mayan/apps/django_gpg/urls.py @@ -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.+)/(?P\w+)/$', 'key_delete', - name='key_delete' + r'^(?P\d+)/$', KeyDetailView.as_view(), name='key_detail' + ), + url( + r'^delete/(?P\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_receive', name='key_receive'), + url( + r'^receive/(?P.+)/$', KeyReceive.as_view(), name='key_receive' + ), ) diff --git a/mayan/apps/django_gpg/views.py b/mayan/apps/django_gpg/views.py index a075aad618..54e575fde6 100644 --- a/mayan/apps/django_gpg/views.py +++ b/mayan/apps/django_gpg/views.py @@ -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() + 'title': _('Delete key'), + '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.' + ) % self.get_object(), } - def get_queryset(self): - return Key.get_all(gpg) - def get_title(self): - return _('Public keys') +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 PrivateKeyListView(PublicKeyListView): - def get_title(self): - return _('Private keys') +class KeyReceive(ConfirmView): + post_action_redirect = reverse_lazy('django_gpg:key_public_list') + view_permission = permission_key_receive - def get_queryset(self): - return Key.get_all(gpg, secret=True) + def get_extra_context(self): + return { + 'message': _('Import key ID: %s?') % self.kwargs['key_id'], + 'title': _('Import key'), + } - -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': + def view_action(self): try: - gpg.delete_key(key) - messages.success(request, _('Key: %s, deleted successfully.') % fingerprint) - return HttpResponseRedirect(next) + Key.objects.receive_key(key_id=self.kwargs['key_id']) 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)) + 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') + }