From 9d23d95ca530f5eabcd0f1c50d18dea2dc29b506 Mon Sep 17 00:00:00 2001 From: J08nY Date: Fri, 18 Aug 2017 18:59:54 +0200 Subject: Add list key management, for owners. --- src/django_pgpmailman/forms.py | 33 ++++++++++++ src/django_pgpmailman/models.py | 15 +++--- .../django_pgpmailman/list/key_management.html | 14 +++-- src/django_pgpmailman/urls.py | 11 ++-- src/django_pgpmailman/views/list.py | 59 ++++++++++++++-------- 5 files changed, 98 insertions(+), 34 deletions(-) diff --git a/src/django_pgpmailman/forms.py b/src/django_pgpmailman/forms.py index 07761df..493d3d8 100644 --- a/src/django_pgpmailman/forms.py +++ b/src/django_pgpmailman/forms.py @@ -16,8 +16,11 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see . from django import forms +from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ +from pgpy import PGPKey +from pgpy.errors import PGPError class NullBooleanRadioSelect(forms.RadioSelect): @@ -163,3 +166,33 @@ class ListMiscSettingsForm(forms.Form): help_text=_('A set of member roles that are allowed to sign the ' 'lists public key via the `key sign` command.') ) + + +class KeyFileField(forms.FileField): + def to_python(self, data): + try: + return PGPKey.from_blob(data.read())[0] + except PGPError as e: + raise ValidationError(str(e), code='invalid') + + +class ListKeyManagementForm(forms.Form): + key = KeyFileField( + widget=forms.ClearableFileInput(), + required=False, + label=_('Upload a new private key'), + help_text=_('Useful for uploading a complately different list key ' + 'than the one generated by mailman-pgp, when setting ' + 'up a new mailing list. The uploaded key must be a ' + 'private PGP key, not expired and must be usable for ' + 'encryption and signatures.') + ) + pubkey = KeyFileField( + widget=forms.ClearableFileInput(), + required=False, + label=_('Upload a public key'), + help_text=_('New signatures from the uploaded key are merged with ' + 'the current list key, provided the uploaded key ' + 'has the same key material and contains the UID that ' + 'was signed.') + ) diff --git a/src/django_pgpmailman/models.py b/src/django_pgpmailman/models.py index 406ae76..0a49475 100644 --- a/src/django_pgpmailman/models.py +++ b/src/django_pgpmailman/models.py @@ -23,7 +23,6 @@ from itertools import chain from mailmanclient._client import MailingList, RESTObject from pgpy import PGPKey from pgpy.errors import PGPError -from six.moves.urllib_error import HTTPError class PGPMailingList(RESTObject): @@ -51,11 +50,8 @@ class PGPMailingList(RESTObject): @key.setter def key(self, value): str_key = str(value) if value else '' - try: - self._connection.call(self._url + '/key', data=dict(key=str_key), - method='PUT') - except HTTPError: - pass + self._connection.call(self._url + '/key', data=dict(key=str_key), + method='PUT') @property def pubkey(self): @@ -65,3 +61,10 @@ class PGPMailingList(RESTObject): return key except PGPError: return None + + @pubkey.setter + def pubkey(self, value): + str_key = str(value) if value else '' + self._connection.call(self._url + '/pubkey', + data=dict(public_key=str_key), + method='PUT') diff --git a/src/django_pgpmailman/templates/django_pgpmailman/list/key_management.html b/src/django_pgpmailman/templates/django_pgpmailman/list/key_management.html index 739c604..9811733 100644 --- a/src/django_pgpmailman/templates/django_pgpmailman/list/key_management.html +++ b/src/django_pgpmailman/templates/django_pgpmailman/list/key_management.html @@ -1,13 +1,19 @@ {% extends "django_pgpmailman/base.html" %} {% load i18n %} - +{% load bootstrap_tags %} {% block head_title %} {% trans 'PGP List' %} - {{ block.super }} {% endblock %} {% block content %} - {% with mlist=pgp_list.mlist %} - {% include 'django_pgpmailman/list/nav.html' with nav_tab='key_management' nav_title='List key management' %} + {% include 'django_pgpmailman/list/nav.html' with nav_tab='key_management' nav_title='List key management' %} - {% endwith %} +

Fingerprint: {{ pgp_list.key.fingerprint }}

+ Download the list private key. +
+ {% bootstrap_form_horizontal form 3 8 'Save changes' %} +
{% endblock content %} diff --git a/src/django_pgpmailman/urls.py b/src/django_pgpmailman/urls.py index abfff27..6276b82 100644 --- a/src/django_pgpmailman/urls.py +++ b/src/django_pgpmailman/urls.py @@ -20,14 +20,16 @@ from __future__ import absolute_import, unicode_literals from django.conf.urls import url, include from django_pgpmailman.views.list import ( - pgp_list_index, pgp_list_summary, pgp_list_pubkey, pgp_list_key_management, + pgp_list_index, pgp_list_summary, ListSignatureSettingsView, ListEncryptionSettingsView, - ListMiscSettingsView) + ListMiscSettingsView, ListKeyManagementView, ListPubkey, ListPrivkey) from django_pgpmailman.views.user import pgp_user_profile list_patterns = [ url(r'^$', pgp_list_summary, name='pgp_list_summary'), - url(r'^key/$', pgp_list_key_management, name='pgp_list_key_management'), + url(r'^key/$', + ListKeyManagementView.as_view(success_url='pgp_list_key_management'), + name='pgp_list_key_management'), url(r'^signatures/$', ListSignatureSettingsView.as_view( success_url='pgp_list_signature_settings'), name='pgp_list_signature_settings'), @@ -37,7 +39,8 @@ list_patterns = [ url(r'^misc/$', ListMiscSettingsView.as_view(success_url='pgp_list_misc_settings'), name='pgp_list_misc_settings'), - url(r'^pubkey$', pgp_list_pubkey, name='pgp_list_pubkey') + url(r'^pubkey$', ListPubkey.as_view(), name='pgp_list_pubkey'), + url(r'^privkey$', ListPrivkey.as_view(), name='pgp_list_privkey') ] user_patterns = [ diff --git a/src/django_pgpmailman/views/list.py b/src/django_pgpmailman/views/list.py index d887d7a..5e64b85 100644 --- a/src/django_pgpmailman/views/list.py +++ b/src/django_pgpmailman/views/list.py @@ -21,11 +21,12 @@ from __future__ import absolute_import, unicode_literals from django.contrib import messages from django.contrib.auth.decorators import login_required from django.core.files.base import ContentFile -from django.http import HttpResponse +from django.http import HttpResponse, Http404 from django.shortcuts import render from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.translation import ugettext_lazy as _ +from django.views import View from django.views.generic import FormView from six.moves.urllib.error import HTTPError @@ -33,7 +34,8 @@ from django_pgpmailman.decorators import (list_view, list_class_view, member_role_required) from django_pgpmailman.forms import (ListSignatureSettingsForm, ListEncryptionSettingsForm, - ListMiscSettingsForm) + ListMiscSettingsForm, + ListKeyManagementForm) from django_pgpmailman.plugin import get_pgp_plugin @@ -48,18 +50,39 @@ def pgp_list_summary(request, pgp_list): {'pgp_list': pgp_list}) -@list_view -def pgp_list_pubkey(request, pgp_list): - pubkey = pgp_list.pubkey - pubkey_file = ContentFile(str(pubkey)) - response = HttpResponse(pubkey_file, 'application/pgp-keys') - response['Content-Length'] = pubkey_file.size - response[ - 'Content-Disposition'] = 'attachment; filename="%s.asc"' % pgp_list.list_id - return response +class ListKey(View): + which_key = None + + def get(self, request): + key = getattr(self.pgp_list, self.which_key) + if key is None: + raise Http404 + key_file = ContentFile(str(key)) + response = HttpResponse(key_file, 'application/pgp-keys') + response['Content-Length'] = key_file.size + response[ + 'Content-Disposition'] = 'attachment; filename="%s.asc"' % self.pgp_list.list_id + return response + + +class ListPubkey(ListKey): + which_key = 'pubkey' + + @list_class_view + def dispatch(self, request, *args, **kwargs): + return super(ListKey, self).dispatch(request, *args, **kwargs) + + +class ListPrivkey(ListKey): + which_key = 'key' + + @method_decorator(login_required) + @list_class_view + @member_role_required('owner') + def dispatch(self, request, *args, **kwargs): + return super(ListPrivkey, self).dispatch(request, *args, **kwargs) -@method_decorator(login_required, name='dispatch') class ListSettings(FormView): properties = None @@ -76,6 +99,7 @@ class ListSettings(FormView): data['mlist'] = self.pgp_list.mlist return data + @method_decorator(login_required) @list_class_view @member_role_required('owner') def dispatch(self, request, *args, **kwargs): @@ -130,11 +154,6 @@ class ListMiscSettingsView(ListSettings): class ListKeyManagementView(ListSettings): - pass - - -@login_required -@list_view -def pgp_list_key_management(request, pgp_list): - return render(request, 'django_pgpmailman/list/key_management.html', - {'pgp_list': pgp_list}) + form_class = ListKeyManagementForm + template_name = 'django_pgpmailman/list/key_management.html' + properties = ('key', 'pubkey') -- cgit v1.2.3-70-g09d2