diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman_pgp/chains/default.py | 3 | ||||
| -rw-r--r-- | src/mailman_pgp/commands/eml_key.py | 2 | ||||
| -rw-r--r-- | src/mailman_pgp/config/__init__.py | 11 | ||||
| -rw-r--r-- | src/mailman_pgp/database/__init__.py | 23 | ||||
| -rw-r--r-- | src/mailman_pgp/model/address.py | 8 | ||||
| -rw-r--r-- | src/mailman_pgp/model/base.py | 9 | ||||
| -rw-r--r-- | src/mailman_pgp/model/list.py | 15 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/inline.py | 47 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/keygen.py | 18 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/mime.py | 40 | ||||
| -rw-r--r-- | src/mailman_pgp/pgp/wrapper.py | 34 | ||||
| -rw-r--r-- | src/mailman_pgp/rules/signature.py | 2 |
12 files changed, 160 insertions, 52 deletions
diff --git a/src/mailman_pgp/chains/default.py b/src/mailman_pgp/chains/default.py index e55e0d3..8fc22bf 100644 --- a/src/mailman_pgp/chains/default.py +++ b/src/mailman_pgp/chains/default.py @@ -15,7 +15,8 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see <http://www.gnu.org/licenses/>. -"""""" +"""A PGP enabled posting chain.""" + from mailman.chains.base import Link from mailman.core.i18n import _ from mailman.interfaces.chain import IChain, LinkAction diff --git a/src/mailman_pgp/commands/eml_key.py b/src/mailman_pgp/commands/eml_key.py index a2194d0..9514f48 100644 --- a/src/mailman_pgp/commands/eml_key.py +++ b/src/mailman_pgp/commands/eml_key.py @@ -25,6 +25,8 @@ from zope.interface import implementer @public @implementer(IEmailCommand) class KeyCommand: + """The `key` command.""" + name = 'key' argument_description = '<change|revoke|sign>' short_description = '' diff --git a/src/mailman_pgp/config/__init__.py b/src/mailman_pgp/config/__init__.py index c3d5719..803b0f9 100644 --- a/src/mailman_pgp/config/__init__.py +++ b/src/mailman_pgp/config/__init__.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see <http://www.gnu.org/licenses/>. -"""""" +"""Mailman PGP configuration module.""" from configparser import ConfigParser @@ -26,10 +26,15 @@ from public.public import public @public class Config(ConfigParser): - def __init__(self): - super().__init__() + """A ConfigParser with a name.""" def load(self, name): + """ + Load the plugin configuration, and set our name. + + :param name: The name to set/load configuration for. + :type name: str + """ self.name = name self.read(expand_path( dict(mailman_config.plugin_configs)[self.name].configuration)) diff --git a/src/mailman_pgp/database/__init__.py b/src/mailman_pgp/database/__init__.py index 950c6b1..bbb7840 100644 --- a/src/mailman_pgp/database/__init__.py +++ b/src/mailman_pgp/database/__init__.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see <http://www.gnu.org/licenses/>. -"""""" +"""Common database functions and class.""" from contextlib import contextmanager @@ -31,6 +31,8 @@ from mailman_pgp.model.base import Base @public class Database: + """A SQLAlchemy database.""" + def __init__(self): url = config.get('db', 'url') self._url = expand(url, None, mailman_config.paths) @@ -41,12 +43,24 @@ class Database: @property def session(self): + """ + Get a scoped_session. + + :return: A scoped session. + :rtype: scoped_session + """ return self._scoped_session() @public @contextmanager def transaction(): + """ + A transaction context manager. + + :return: A session for convenience. + :rtype: scoped_session + """ try: yield config.db.session except: @@ -58,4 +72,11 @@ def transaction(): @public def query(cls): + """ + A query helper. + + :param cls: Class to query. + :return: A query on the class. + :rtype: sqlalchemy.orm.query.Query + """ return config.db.session.query(cls) diff --git a/src/mailman_pgp/model/address.py b/src/mailman_pgp/model/address.py index 60c6e2f..1dc3840 100644 --- a/src/mailman_pgp/model/address.py +++ b/src/mailman_pgp/model/address.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see <http://www.gnu.org/licenses/>. -"""""" +"""Model for PGP enabled addresses.""" from os.path import exists, isfile, join @@ -29,6 +29,8 @@ from mailman_pgp.model.base import Base class PGPAddress(Base): + """A PGP enabled address.""" + __tablename__ = 'pgp_addresses' id = Column(Integer, primary_key=True) @@ -46,6 +48,8 @@ class PGPAddress(Base): @property def key(self): + if self.key_fingerprint is None: + return None if self._key is None: if exists(self.key_path) and isfile(self.key_path): self._key, _ = PGPKey.from_file(self.key_path) @@ -53,5 +57,7 @@ class PGPAddress(Base): @property def key_path(self): + if self.key_fingerprint is None: + return None return join(config.pgp.keydir_config['user_keydir'], self.key_fingerprint + '.asc') diff --git a/src/mailman_pgp/model/base.py b/src/mailman_pgp/model/base.py index 646bb7d..f5d8e77 100644 --- a/src/mailman_pgp/model/base.py +++ b/src/mailman_pgp/model/base.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see <http://www.gnu.org/licenses/>. -"""""" +"""Base class for all models.""" from public import public from sqlalchemy.ext.declarative import as_declarative @@ -26,9 +26,16 @@ from mailman_pgp.database import query @public @as_declarative() class Base: + """Custom declarative base.""" @classmethod def query(cls): + """ + A query helper. + + :return: A query on class. + :rtype: sqlalchemy.orm.query.Query + """ return query(cls) diff --git a/src/mailman_pgp/model/list.py b/src/mailman_pgp/model/list.py index 84dd1a8..e1de7cc 100644 --- a/src/mailman_pgp/model/list.py +++ b/src/mailman_pgp/model/list.py @@ -15,18 +15,18 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see <http://www.gnu.org/licenses/>. -"""""" +"""Model for PGP enabled mailing lists.""" from os.path import exists, isfile, join -from mailman.config import config as mailman_config from mailman.database.types import Enum, SAUnicode from mailman.interfaces.action import Action -from mailman.model.mailinglist import MailingList +from mailman.interfaces.listmanager import IListManager from pgpy import PGPKey from public import public from sqlalchemy import Boolean, Column, Integer from sqlalchemy.orm import reconstructor +from zope.component import getUtility from mailman_pgp.config import config from mailman_pgp.model.base import Base @@ -35,6 +35,8 @@ from mailman_pgp.pgp.keygen import ListKeyGenerator @public class PGPMailingList(Base): + """A PGP enabled mailing list.""" + __tablename__ = 'pgp_lists' id = Column(Integer, primary_key=True) @@ -76,10 +78,9 @@ class PGPMailingList(Base): @property def mlist(self): - if self._mlist is not None: - return self._mlist - return mailman_config.db.store.query(MailingList).filter_by( - _list_id=self.list_id).first() + if self._mlist is None: + self._mlist = getUtility(IListManager).get_by_list_id(self.list_id) + return self._mlist @property def key(self): diff --git a/src/mailman_pgp/pgp/inline.py b/src/mailman_pgp/pgp/inline.py index 646d13a..d87b3da 100644 --- a/src/mailman_pgp/pgp/inline.py +++ b/src/mailman_pgp/pgp/inline.py @@ -16,16 +16,23 @@ # this program. If not, see <http://www.gnu.org/licenses/>. """Strict inline PGP message wrapper.""" -from email.message import Message -from pgpy import PGPKey, PGPMessage +from pgpy import PGPMessage from pgpy.types import Armorable from public import public @public class InlineWrapper: - def __init__(self, msg: Message): + """Inline PGP wrapper.""" + + def __init__(self, msg): + """ + Wrap the given message. + + :param msg: The message to wrap. + :type msg: mailman.email.message.Message + """ self.msg = msg def _is_inline(self): @@ -45,32 +52,50 @@ class InlineWrapper: def is_signed(self): """ + Whether the message is inline signed (cleartext). - :return: + :return: If the message is inline signed. + :rtype: bool """ + # XXX: This doesnt handle non-cleartext signatures (gpg -s -a) return self._is_inline() and self._has_armor('SIGNATURE') def is_encrypted(self): """ + Whether the message is inline encrypted. - :return: + :return: If the message is inline encrypted. + :rtype: bool """ + # XXX: This mistakes non-cleartext signature as encrypted. return self._is_inline() and self._has_armor('MESSAGE') - def verify(self, key: PGPKey): + def verify(self, key): """ + Verify the signature of this message with key. - :param key: - :return: + :param key: The key to verify with. + :type key: pgpy.PGPKey + :return: The verified signature. + :rtype: pgpy.types.SignatureVerification """ message = PGPMessage.from_blob(self._as_string()) return key.verify(message) - def decrypt(self, key: PGPKey): + def sign(self): + pass + + def decrypt(self, key): """ + Decrypt this message with key. - :param key: - :return: + :param key: The key to decrypt with. + :type key: pgpy.PGPKey + :return: The decrypted message. + :rtype: PGPMessage """ message = PGPMessage.from_blob(self._as_string()) return key.decrypt(message) + + def encrypt(self): + pass diff --git a/src/mailman_pgp/pgp/keygen.py b/src/mailman_pgp/pgp/keygen.py index bff5450..06ca22b 100644 --- a/src/mailman_pgp/pgp/keygen.py +++ b/src/mailman_pgp/pgp/keygen.py @@ -28,22 +28,20 @@ from pgpy.constants import ( class ListKeyGenerator(mp.Process): - """""" + """A multiprocessing list key generator.""" def __init__(self, keypair_config, display_name, posting_address, request_address, key_path): super().__init__( target=self.generate, - args=( - keypair_config, display_name, posting_address, request_address, - key_path), - daemon=True) + args=(keypair_config, display_name, posting_address, + request_address, key_path)) def generate(self, keypair_config, display_name, posting_address, request_address, key_path): """ - Generates the list keypair and saves it to key_path, if it does not - exist. + Generate the list keypair and save it, if it does not exist. + :param keypair_config: :param display_name: :param posting_address: @@ -59,8 +57,9 @@ class ListKeyGenerator(mp.Process): def _create(self, config, display_name, posting_address, request_address): """ - Generates the list `PGPKey` keypair, with posting and request UIDs. - Uses a Sign+Certify main key and Encrypt subkey. + Generate the list `PGPKey` keypair, with posting and request UIDs. + + Use a Sign+Certify main key and Encrypt subkey. :param config: :param display_name: :param posting_address: @@ -105,6 +104,7 @@ class ListKeyGenerator(mp.Process): def _save(self, key, key_path): """ Save the generated key. + :param key: :param key_path: """ diff --git a/src/mailman_pgp/pgp/mime.py b/src/mailman_pgp/pgp/mime.py index b8ab882..fde9d49 100644 --- a/src/mailman_pgp/pgp/mime.py +++ b/src/mailman_pgp/pgp/mime.py @@ -16,19 +16,27 @@ # this program. If not, see <http://www.gnu.org/licenses/>. """RFC1847 and RFC3156 compliant message wrapped.""" -from email.message import Message + from email.utils import collapse_rfc2231_value -from pgpy import PGPKey, PGPMessage, PGPSignature +from pgpy import PGPMessage, PGPSignature from public import public @public class MIMEWrapper: + """PGP/MIME (RFC1847 + RFC3156) compliant wrapper.""" + _signed_subtype = 'application/pgp-signature' _encrypted_subtype = 'application/pgp-encrypted' - def __init__(self, msg: Message): + def __init__(self, msg): + """ + Wrap the given message. + + :param msg: The message to wrap. + :type msg: mailman.email.message.Message + """ self.msg = msg def _is_mime(self): @@ -40,7 +48,9 @@ class MIMEWrapper: def is_signed(self): """ Whether the whole message is MIME signed as per RFC3156 section 5. - :return: + + :return: If the message is MIME signed. + :rtype: bool """ if not self._is_mime(): return False @@ -56,7 +66,9 @@ class MIMEWrapper: def is_encrypted(self): """ Whether the whole message is MIME encrypted as per RFC3156 section 4. - :return: + + :return: If the message is MIME encrypted. + :rtype: bool """ if not self._is_mime(): return False @@ -73,11 +85,14 @@ class MIMEWrapper: content_subtype == 'encrypted' and \ protocol_param == MIMEWrapper._encrypted_subtype - def verify(self, key: PGPKey): + def verify(self, key): """ + Verify the signature of this message with key. - :param key: - :return: + :param key: The key to verify with. + :type key: pgpy.PGPKey + :return: The verified signature. + :rtype: pgpy.types.SignatureVerification """ clear_text = self.msg.get_payload(0).as_string() sig_text = self.msg.get_payload(1).get_payload() @@ -87,11 +102,14 @@ class MIMEWrapper: def sign(self): pass - def decrypt(self, key: PGPKey): + def decrypt(self, key): """ + Decrypt this message with key. - :param key: - :return: + :param key: The key to decrypt with. + :type key: pgpy.PGPKey + :return: The decrypted message. + :rtype: PGPMessage """ msg_text = self.msg.get_payload(1).get_payload() msg = PGPMessage.from_blob(msg_text) diff --git a/src/mailman_pgp/pgp/wrapper.py b/src/mailman_pgp/pgp/wrapper.py index 5d8bf41..9a004c4 100644 --- a/src/mailman_pgp/pgp/wrapper.py +++ b/src/mailman_pgp/pgp/wrapper.py @@ -14,10 +14,8 @@ # 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 email.message import Message +"""A combined PGP/MIME + inline PGP wrapper.""" -from pgpy import PGPKey from public import public from mailman_pgp.pgp.inline import InlineWrapper @@ -26,7 +24,15 @@ from mailman_pgp.pgp.mime import MIMEWrapper @public class PGPWrapper(): - def __init__(self, msg: Message): + """A combined PGP/MIME + inline PGP wrapper.""" + + def __init__(self, msg): + """ + Wrap the given message. + + :param msg: The message to wrap. + :type msg: mailman.email.message.Message + """ self.msg = msg self.mime = MIMEWrapper(msg) self.inline = InlineWrapper(msg) @@ -40,7 +46,15 @@ class PGPWrapper(): def is_signed(self): return self.is_mime_signed() or self.is_inline_signed() - def verify(self, key: PGPKey): + def verify(self, key): + """ + Verify the signature of this message with key. + + :param key: The key to verify with. + :type key: pgpy.PGPKey + :return: The verified signature. + :rtype: pgpy.types.SignatureVerification + """ if self.is_mime_signed(): return self.mime.verify(key) else: @@ -55,7 +69,15 @@ class PGPWrapper(): def is_encrypted(self): return self.is_mime_encrypted() or self.is_inline_encrypted() - def decrypt(self, key: PGPKey): + def decrypt(self, key): + """ + Decrypt this message with key. + + :param key: The key to decrypt with. + :type key: pgpy.PGPKey + :return: The decrypted message. + :rtype: PGPMessage + """ if self.is_mime_encrypted(): return self.mime.decrypt(key) else: diff --git a/src/mailman_pgp/rules/signature.py b/src/mailman_pgp/rules/signature.py index 0ffd76b..5f5b802 100644 --- a/src/mailman_pgp/rules/signature.py +++ b/src/mailman_pgp/rules/signature.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License along with # this program. If not, see <http://www.gnu.org/licenses/>. -"""""" +"""Signature checking rule for the pgp-posting-chain.""" from mailman.core.i18n import _ from mailman.interfaces.rules import IRule |
