diff options
| author | J08nY | 2017-07-07 23:09:39 +0200 |
|---|---|---|
| committer | J08nY | 2017-07-07 23:14:49 +0200 |
| commit | 0ba497780cbe1abe9b91321993e31456bfcf1cd0 (patch) | |
| tree | a4a070e1ba65d2c509e3b35d384f662a621ef81d /src | |
| parent | 2b6d4eca265617d1acfbbc790350055af86268dd (diff) | |
| download | mailman-pgp-0ba497780cbe1abe9b91321993e31456bfcf1cd0.tar.gz mailman-pgp-0ba497780cbe1abe9b91321993e31456bfcf1cd0.tar.zst mailman-pgp-0ba497780cbe1abe9b91321993e31456bfcf1cd0.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman_pgp/commands/eml_key.py | 88 | ||||
| -rw-r--r-- | src/mailman_pgp/workflows/__init__.py | 0 | ||||
| -rw-r--r-- | src/mailman_pgp/workflows/base.py | 97 | ||||
| -rw-r--r-- | src/mailman_pgp/workflows/subscription.py | 137 | ||||
| -rw-r--r-- | src/mailman_pgp/workflows/tests/__init__.py | 0 | ||||
| -rw-r--r-- | src/mailman_pgp/workflows/tests/test_base.py | 16 | ||||
| -rw-r--r-- | src/mailman_pgp/workflows/tests/test_subscription.py | 16 |
7 files changed, 351 insertions, 3 deletions
diff --git a/src/mailman_pgp/commands/eml_key.py b/src/mailman_pgp/commands/eml_key.py index 9514f48..b280603 100644 --- a/src/mailman_pgp/commands/eml_key.py +++ b/src/mailman_pgp/commands/eml_key.py @@ -16,11 +16,21 @@ # this program. If not, see <http://www.gnu.org/licenses/>. """The key email command.""" +from email.utils import parseaddr from mailman.interfaces.command import ContinueProcessing, IEmailCommand +from mailman.interfaces.subscriptions import ISubscriptionManager +from mailman.interfaces.usermanager import IUserManager from public import public +from zope.component import getUtility from zope.interface import implementer +from mailman_pgp.database import transaction +from mailman_pgp.model.address import PGPAddress +from mailman_pgp.model.list import PGPMailingList +from mailman_pgp.pgp.wrapper import PGPWrapper +from mailman_pgp.workflows.base import CONFIRM_REQUEST + @public @implementer(IEmailCommand) @@ -28,7 +38,7 @@ class KeyCommand: """The `key` command.""" name = 'key' - argument_description = '<change|revoke|sign>' + argument_description = '<set|confirm|change|revoke|sign>' short_description = '' description = '' @@ -38,7 +48,78 @@ class KeyCommand: print('No sub-command specified,' ' must be one of <change|revoke|sign>.', file=results) return ContinueProcessing.no - if arguments[0] == 'change': + + pgp_list = PGPMailingList.for_list(mlist) + if pgp_list is None: + print('This mailing list doesnt have pgp enabled.', file=results) + return ContinueProcessing.yes # XXX: What does this even mean? + + if arguments[0] == 'set': + if len(arguments) != 2: + print('Missing token', file=results) + return ContinueProcessing.no + + wrapped = PGPWrapper(msg) + if not wrapped.has_keys(): + print('No keys here?') + return ContinueProcessing.no + + keys = list(wrapped.keys()) + if len(keys) != 1: + print('More than one key! Which one is yours?') + return ContinueProcessing.no + + with transaction() as t: + pgp_address = PGPAddress(self.address) + pgp_address.key = keys.pop() + t.add(pgp_address) + + token = arguments[1] + ISubscriptionManager(mlist).confirm(token) + # XXX finish this + elif arguments[0] == 'confirm': + if len(arguments) != 2: + print('Missing token', file=results) + return ContinueProcessing.no + + display_name, email = parseaddr(msg['from']) + # Address could be None or the empty string. + if not email: + email = msg.sender + if not email: + print('No email to subscribe with.', file=results) + return ContinueProcessing.no + um = getUtility(IUserManager) + + pgp_address = PGPAddress.for_address(um.get_address(email)) + if pgp_address is None: + # TODO + pass + + wrapped = PGPWrapper(msg) + if wrapped.is_encrypted(): + decrypted = wrapped.decrypt(pgp_list.key) + wrapped = PGPWrapper(decrypted) + + if not wrapped.is_signed(): + print('Message not signed, ignoring.', file=results) + return ContinueProcessing.no + + if not wrapped.verifies(pgp_address.key): + print('Message failed to verify.', file=results) + return ContinueProcessing.no + + token = arguments[1] + + expecting = CONFIRM_REQUEST.format(pgp_address.key.pubkey, + token) + if wrapped.get_signed() == expecting: + ISubscriptionManager(mlist).confirm(token) + else: + # TODO + pass + + elif arguments[0] == 'change': # New public key in attachment, requires to be signed with current # key pass @@ -52,4 +133,5 @@ class KeyCommand: else: print('Wrong sub-command specified,' ' must be one of <change|revoke|sign>.', file=results) - return ContinueProcessing.no + + return ContinueProcessing.no diff --git a/src/mailman_pgp/workflows/__init__.py b/src/mailman_pgp/workflows/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/mailman_pgp/workflows/__init__.py diff --git a/src/mailman_pgp/workflows/base.py b/src/mailman_pgp/workflows/base.py new file mode 100644 index 0000000..40ff4d7 --- /dev/null +++ b/src/mailman_pgp/workflows/base.py @@ -0,0 +1,97 @@ +# Copyright (C) 2017 Jan Jancar +# +# This file is a part of the Mailman PGP plugin. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see <http://www.gnu.org/licenses/>. + +"""""" +from mailman.email.message import UserNotification +from mailman.interfaces.subscriptions import TokenOwner + +from mailman_pgp.model.address import PGPAddress +from mailman_pgp.model.list import PGPMailingList +from mailman_pgp.pgp.wrapper import PGPWrapper + +KEY_REQUEST = """\ +---------- +TODO: this is a pgp enabled list. +We need your pubkey. +Reply to this message with it as a PGP/MIME(preferred) or inline. +----------""" + +CONFIRM_REQUEST = """\ +---------- +TODO: this is a pgp enabled list. +Reply to this message with this whole text +signed with your supplied key, either inline or PGP/MIME. + +Fingerprint: {} +Token: {} +---------- +""" + + +class PubkeyMixin: + def __init__(self, pubkey=None): + self.pubkey = pubkey + + def _step_pubkey_checks(self): + if not self.pubkey: + self.push('send_key_request') + + def _step_send_key_request(self): + self._set_token(TokenOwner.subscriber) + self.push('receive_key') + self.save() + request_address = self.mlist.request_address + email_address = self.address.email + msg = UserNotification(email_address, request_address, + 'key set {}'.format(self.token), + KEY_REQUEST) + msg.send(self.mlist, add_precedence=False) + # Now we wait for the confirmation. + raise StopIteration + + def _step_receive_key(self): + pgp_address = PGPAddress.for_address(self.address) + if pgp_address is None or pgp_address.key: + # The workflow was confirmed but we still dont have an address + # or the pubkey. So resend request and wait. + self.push('send_key_request') + return + else: + self.pubkey = pgp_address.key + self.push('send_confirm_request') + + def _step_send_confirm_request(self): + self._set_token(TokenOwner.subscriber) + self.push('receive_confirmation') + self.save() + request_address = self.mlist.request_address + email_address = self.address.email + msg = UserNotification(email_address, request_address, + 'key confirm {}'.format(self.token), + CONFIRM_REQUEST.format(self.pubkey.fingerprint, + self.token)) + pgp_list = PGPMailingList.for_list(self.mlist) + wrapped = PGPWrapper(msg) + encrypted = wrapped.encrypt(self.pubkey, pgp_list.pubkey) + + # XXX: This is not good: + msg.set_payload(encrypted) + msg.send(self.mlist) + raise StopIteration + + def _step_receive_confirmation(self): + pass diff --git a/src/mailman_pgp/workflows/subscription.py b/src/mailman_pgp/workflows/subscription.py new file mode 100644 index 0000000..a571b44 --- /dev/null +++ b/src/mailman_pgp/workflows/subscription.py @@ -0,0 +1,137 @@ +# Copyright (C) 2017 Jan Jancar +# +# This file is a part of the Mailman PGP plugin. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see <http://www.gnu.org/licenses/>. + +"""""" + +from mailman.core.i18n import _ +from mailman.interfaces.workflows import ISubscriptionWorkflow +from mailman.workflows.common import (ConfirmationMixin, ModerationMixin, + SubscriptionBase, VerificationMixin) +from public import public +from zope.interface import implementer + +from mailman_pgp.workflows.base import PubkeyMixin + + +@public +@implementer(ISubscriptionWorkflow) +class ConfimSubscriptionPolicy(SubscriptionBase, VerificationMixin, + ConfirmationMixin, PubkeyMixin): + """""" + + name = 'pgp-policy-confirm' + description = _('An subscription policy, for a PGP-enabled mailing list ' + 'that requires confirmation.') + initial_state = 'prepare' + save_attributes = ( + 'verified', + 'confirmed', + 'pubkey', + 'address_key', + 'subscriber_key', + 'user_key', + 'token_owner_key', + ) + + def __init__(self, mlist, subscriber=None, *, + pre_verified=False, pre_confirmed=False, pubkey=None): + SubscriptionBase.__init__(self, mlist, subscriber) + VerificationMixin.__init__(self, pre_verified=pre_verified) + ConfirmationMixin.__init__(self, pre_confirmed=pre_confirmed) + PubkeyMixin.__init__(self, pubkey=pubkey) + + def _step_prepare(self): + self.push('do_subscription') + self.push('pubkey_checks') + self.push('confirmation_checks') + self.push('verification_checks') + self.push('sanity_checks') + + +@public +@implementer(ISubscriptionWorkflow) +class ModerationSubscriptionPolicy(SubscriptionBase, VerificationMixin, + ModerationMixin, PubkeyMixin): + """""" + + name = 'pgp-policy-moderate' + description = _('An subscription policy, for a PGP-enabled mailing list ' + 'that requires moderation.') + initial_state = 'prepare' + save_attributes = ( + 'verified', + 'approved', + 'pubkey', + 'address_key', + 'subscriber_key', + 'user_key', + 'token_owner_key', + ) + + def __init__(self, mlist, subscriber=None, *, + pre_verified=False, pre_approved=False, pubkey=None): + SubscriptionBase.__init__(self, mlist, subscriber) + VerificationMixin.__init__(self, pre_verified=pre_verified) + ModerationMixin.__init__(self, pre_approved=pre_approved) + PubkeyMixin.__init__(self, pubkey=pubkey) + + def _step_prepare(self): + self.push('do_subscription') + self.push('moderation_checks') + self.push('pubkey_checks') + self.push('verification_checks') + self.push('sanity_checks') + + +@public +@implementer(ISubscriptionWorkflow) +class ConfirmModerationSubscriptionPolicy(SubscriptionBase, VerificationMixin, + ConfirmationMixin, ModerationMixin, + PubkeyMixin): + """""" + + name = 'pgp-policy-confirm-moderate' + description = _('An subscription policy, for a PGP-enabled mailing list ' + 'that requires moderation after confirmation.') + initial_state = 'prepare' + save_attributes = ( + 'verified', + 'confirmed', + 'approved', + 'pubkey', + 'address_key', + 'subscriber_key', + 'user_key', + 'token_owner_key', + ) + + def __init__(self, mlist, subscriber=None, *, + pre_verified=False, pre_confirmed=False, pre_approved=False, + pubkey=None): + SubscriptionBase.__init__(self, mlist, subscriber) + VerificationMixin.__init__(self, pre_verified=pre_verified) + ConfirmationMixin.__init__(self, pre_confirmed=pre_confirmed) + ModerationMixin.__init__(self, pre_approved=pre_approved) + PubkeyMixin.__init__(self, pubkey=pubkey) + + def _step_prepare(self): + self.push('do_subscription') + self.push('moderation_checks') + self.push('pubkey_checks') + self.push('confirmation_checks') + self.push('verification_checks') + self.push('sanity_checks') diff --git a/src/mailman_pgp/workflows/tests/__init__.py b/src/mailman_pgp/workflows/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/mailman_pgp/workflows/tests/__init__.py diff --git a/src/mailman_pgp/workflows/tests/test_base.py b/src/mailman_pgp/workflows/tests/test_base.py new file mode 100644 index 0000000..8b6b4d1 --- /dev/null +++ b/src/mailman_pgp/workflows/tests/test_base.py @@ -0,0 +1,16 @@ +# Copyright (C) 2017 Jan Jancar +# +# This file is a part of the Mailman PGP plugin. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/src/mailman_pgp/workflows/tests/test_subscription.py b/src/mailman_pgp/workflows/tests/test_subscription.py new file mode 100644 index 0000000..8b6b4d1 --- /dev/null +++ b/src/mailman_pgp/workflows/tests/test_subscription.py @@ -0,0 +1,16 @@ +# Copyright (C) 2017 Jan Jancar +# +# This file is a part of the Mailman PGP plugin. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see <http://www.gnu.org/licenses/>. |
