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 datetime import datetime
from django.utils.translation import ugettext_lazy as _ 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 common.classes import Package
from navigation import SourceColumn from navigation import SourceColumn
from .api import Key, KeyStub from .classes import KeyStub
from .links import ( from .links import (
link_key_delete, link_key_query, link_key_receive, link_key_setup, link_key_delete, link_key_detail, link_key_query, link_key_receive,
link_public_keys link_key_setup, link_private_keys, link_public_keys
) )
from .permissions import permission_key_delete, permission_key_view
class DjangoGPGApp(MayanAppConfig): class DjangoGPGApp(MayanAppConfig):
@@ -24,6 +30,15 @@ class DjangoGPGApp(MayanAppConfig):
def ready(self): def ready(self):
super(DjangoGPGApp, self).ready() 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=''' Package(label='python-gnupg', license_text='''
Copyright (c) 2008-2014 by Vinay Sajip. Copyright (c) 2008-2014 by Vinay Sajip.
All rights reserved. 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. ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''') ''')
SourceColumn(source=Key, label=_('ID'), attribute='key_id') SourceColumn(source=Key, label=_('Key ID'), attribute='key_id')
SourceColumn( SourceColumn(source=Key, label=_('User ID'), attribute='user_id')
source=Key, label=_('Owner'),
func=lambda context: ', '.join(context['object'].uids)
)
SourceColumn( SourceColumn(
source=KeyStub, label=_('ID'), 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=_('Length'), attribute='length')
SourceColumn( SourceColumn(
source=KeyStub, label=_('Identities'), source=KeyStub, label=_('User ID'),
func=lambda context: ', '.join(context['object'].uids) 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_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_setup.bind_links(links=(link_key_setup,))
menu_sidebar.bind_links( menu_facet.bind_links(
links=(link_public_keys, link_key_query), links=(link_private_keys, link_public_keys),
sources=( sources=(
'django_gpg:key_delete', 'django_gpg:key_public_list', 'django_gpg:key_public_list', 'django_gpg:key_private_list',
'django_gpg:key_query', 'django_gpg:key_query_results', '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 __future__ import unicode_literals
from django import forms from django import forms
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _ 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): class KeySearchForm(forms.Form):
term = forms.CharField( term = forms.CharField(

View File

@@ -10,18 +10,21 @@ from .permissions import (
) )
link_private_keys = Link( link_private_keys = Link(
icon='fa fa-key', permissions=(permission_key_view,), permissions=(permission_key_view,), text=_('Private keys'),
text=_('Private keys'), view='django_gpg:key_private_list' view='django_gpg:key_private_list'
) )
link_public_keys = Link( link_public_keys = Link(
icon='fa fa-key', permissions=(permission_key_view,), permissions=(permission_key_view,), text=_('Public keys'),
text=_('Public keys'), view='django_gpg:key_public_list' view='django_gpg:key_public_list'
) )
link_key_delete = Link( link_key_delete = Link(
permissions=(permission_key_delete,), tags='dangerous', text=_('Delete'), 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( link_key_query = Link(
permissions=(permission_keyserver_query,), text=_('Query keyservers'), permissions=(permission_keyserver_query,), text=_('Query keyservers'),
view='django_gpg:key_query' view='django_gpg:key_query'

View File

@@ -61,3 +61,8 @@ SIGNATURE_STATES = {
'text': _('Document is signed with a valid signature.'), '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 import gnupg
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .literals import KEY_TYPE_CHOICES, KEY_TYPE_SECRET
from .exceptions import NeedPassphrase, PassphraseError 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__) logger = logging.getLogger(__name__)
@@ -39,45 +41,6 @@ def gpg_command(function):
return result 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 @python_2_unicode_compatible
class Key(models.Model): class Key(models.Model):
key_data = models.TextField(verbose_name=_('Key data')) key_data = models.TextField(verbose_name=_('Key data'))
@@ -122,6 +85,9 @@ class Key(models.Model):
if not import_results.count: if not import_results.count:
raise ValidationError('Invalid key data') raise ValidationError('Invalid key data')
def get_absolute_url(self):
return reverse('django_gpg:key_detail', args=(self.pk,))
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
temporary_directory = tempfile.mkdtemp() temporary_directory = tempfile.mkdtemp()

View File

@@ -6,15 +6,15 @@ from permissions import PermissionNamespace
namespace = PermissionNamespace('django_gpg', _('Key management')) namespace = PermissionNamespace('django_gpg', _('Key management'))
permission_key_view = namespace.add_permission(
name='key_view', label=_('View keys')
)
permission_key_delete = namespace.add_permission( permission_key_delete = namespace.add_permission(
name='key_delete', label=_('Delete keys') name='key_delete', label=_('Delete keys')
) )
permission_keyserver_query = namespace.add_permission(
name='keyserver_query', label=_('Query keyservers')
)
permission_key_receive = namespace.add_permission( permission_key_receive = namespace.add_permission(
name='key_receive', label=_('Import keys from keyservers') 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', global_name='SIGNATURES_KEYSERVER', default='pool.sks-keyservers.net',
help_text=_('Keyserver used to query for keys.') 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 django.conf.urls import patterns, url
from .views import ( from .views import (
KeyQueryView, KeyQueryResultView, PrivateKeyListView, PublicKeyListView KeyDeleteView, KeyDetailView, KeyQueryView, KeyQueryResultView, KeyReceive,
PrivateKeyListView, PublicKeyListView
) )
urlpatterns = patterns( urlpatterns = patterns(
'django_gpg.views', 'django_gpg.views',
url( url(
r'^delete/(?P<fingerprint>.+)/(?P<key_type>\w+)/$', 'key_delete', r'^(?P<pk>\d+)/$', KeyDetailView.as_view(), name='key_detail'
name='key_delete' ),
url(
r'^delete/(?P<pk>\d+)/$', KeyDeleteView.as_view(), name='key_delete'
), ),
url( url(
r'^list/private/$', PrivateKeyListView.as_view(), r'^list/private/$', PrivateKeyListView.as_view(),
@@ -24,5 +27,7 @@ urlpatterns = patterns(
r'^query/results/$', KeyQueryResultView.as_view(), r'^query/results/$', KeyQueryResultView.as_view(),
name='key_query_results' 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 import logging
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponseRedirect
from django.shortcuts import redirect, render_to_response
from django.template import RequestContext
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.generics import SimpleView, SingleObjectListView from common.generics import (
from permissions import Permission ConfirmView, SimpleView, SingleObjectDeleteView, SingleObjectDetailView,
SingleObjectListView
)
from .api import Key from .forms import KeyDetailForm, KeySearchForm
from .forms import KeySearchForm from .models import Key
from .permissions import ( from .permissions import (
permission_key_delete, permission_key_receive, permission_key_view, permission_key_delete, permission_key_receive, permission_key_view,
permission_keyserver_query permission_keyserver_query
) )
from .runtime import gpg
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def key_receive(request, key_id): class KeyDeleteView(SingleObjectDeleteView):
Permission.check_permissions(request.user, (permission_key_receive,)) model = Key
object_permission = permission_key_delete
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
def get_extra_context(self): def get_extra_context(self):
return { 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'), 'title': _('Delete key'),
'delete_view': True,
'message': _( 'message': _(
'Delete key %s? If you delete a public key that is part of a ' '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.' 'public/private pair the private key will be deleted as well.'
) % key, ) % self.get_object(),
'next': next, }
'previous': previous,
}, context_instance=RequestContext(request))
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): class KeyQueryView(SimpleView):
@@ -149,6 +109,28 @@ class KeyQueryResultView(SingleObjectListView):
def get_queryset(self): def get_queryset(self):
term = self.request.GET.get('term') term = self.request.GET.get('term')
if term: if term:
return gpg.query(term) return Key.objects.search(query=term)
else: else:
return () 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')
}