From c236b6825a79d61e15be22c65c3b8e7c27a92f2f Mon Sep 17 00:00:00 2001 From: s3lph <1375407-s3lph@users.noreply.gitlab.com> Date: Sat, 16 Apr 2022 19:06:53 +0200 Subject: [PATCH] Add test case with real schleuder --- .gitignore | 2 +- .gitlab-ci.yml | 46 +++++++++- multischleuder.yml | 58 +++++++++++++ multischleuder/__main__.py | 5 ++ multischleuder/api.py | 5 +- multischleuder/conflict.py | 6 +- multischleuder/main.py | 9 +- multischleuder/test/test_api.py | 76 ++++++++-------- multischleuder/test/test_types.py | 23 +++-- multischleuder/types.py | 13 ++- .../etc/multischleuder/multischleuder.yml | 58 +++++++++++++ test/multischleuder.yml | 70 +++++++++++++++ test/prepare-schleuder.sh | 86 +++++++++++++++++++ test/report.sh | 19 ++++ 14 files changed, 417 insertions(+), 59 deletions(-) create mode 100644 multischleuder.yml create mode 100644 multischleuder/__main__.py create mode 100644 package/debian/multischleuder/etc/multischleuder/multischleuder.yml create mode 100644 test/multischleuder.yml create mode 100755 test/prepare-schleuder.sh create mode 100755 test/report.sh diff --git a/.gitignore b/.gitignore index ffc0a1f..c44f17b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ **/.mypy_cache/ ca.pem -multischleuder.yml \ No newline at end of file +./multischleuder.yml \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4b67a04..a7b34e1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,6 +3,7 @@ image: python:3.9-bullseye stages: - test +- coverage - build - deploy @@ -19,8 +20,8 @@ test: script: - pip3 install -e . - python3 -m coverage run --rcfile=setup.cfg -m unittest discover multischleuder - - python3 -m coverage combine - - python3 -m coverage report --rcfile=setup.cfg + artifacts: + - ".coverage*" codestyle: stage: test @@ -35,6 +36,47 @@ mypy: - mypy --install-types --non-interactive multischleuder - mypy multischleuder +schleuder: + stage: test + script: + - debconf-set-selections <<<"postfix postfix/mailname string example.org" + - debconf-set-selections <<<"postfix postfix/main_mailer_type string 'Local only'" + - apt update; apt install --yes schleuder schleuder-cli postfix + - /usr/lib/postfix/configure-instance.sh - + - echo "virtual_alias_maps = static:root" >> /etc/postfix/main.cf + - /usr/sbin/postmulti -i - -p start + - schleuder-cli lists list || true + - export CERT_FPR=$(schleuder cert fingerprint | cut -d' ' -f4) + - echo " - '00000000000000000000000000000000'" >> /etc/schleuder/schleuder.yml + - | + cat > ~/.schleuder-cli/schleuder-cli.yml < 0: + return json.loads(respdata) + return None def dry_run(self): self._dry_run = True diff --git a/multischleuder/conflict.py b/multischleuder/conflict.py index ef1c6ac..227025a 100644 --- a/multischleuder/conflict.py +++ b/multischleuder/conflict.py @@ -163,7 +163,11 @@ class KeyConflictResolution: now = int(datetime.utcnow().timestamp()) with open(self._state_file, 'a+') as f: f.seek(0) - state: Dict[str, int] = json.load(f) + try: + state: Dict[str, int] = json.load(f) + except: + # TODO: This could lead to a situation where multischleuder becomes a spammer + state = {} # Remove all state entries older than conflict_interval state = {k: v for k, v in state.items() if now-v < self._interval} # Remove all messages not already sent recently diff --git a/multischleuder/main.py b/multischleuder/main.py index c4e415f..e71e27c 100644 --- a/multischleuder/main.py +++ b/multischleuder/main.py @@ -2,6 +2,8 @@ from typing import Any, Dict, List import argparse +import logging +import sys import yaml @@ -60,12 +62,9 @@ def main(): ap.add_argument('--version', action='version', version=__version__) ns = ap.parse_args(sys.argv[1:]) if ns.verbose: - logger = logging.getLogger().setLevel('DEBUG') + logger = logging.getLogger() + logger.setLevel('DEBUG') logger.debug('Verbose logging enabled') lists = parse_config(ns) for lst in lists: lst.process(ns.dry_run) - - -if __name__ == '__main__': - main() diff --git a/multischleuder/test/test_api.py b/multischleuder/test/test_api.py index b30221a..c2a56cf 100644 --- a/multischleuder/test/test_api.py +++ b/multischleuder/test/test_api.py @@ -69,8 +69,8 @@ _SUBSCRIBER_RESPONSE = ''' { "id": 23, "list_id": 42, - "email": "foo@example.org", - "fingerprint": "2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6", + "email": "andy.example@example.org", + "fingerprint": "ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9", "admin": false, "delivery_enabled": true, "created_at": "2022-04-15T01:11:12.123Z", @@ -84,7 +84,7 @@ _SUBSCRIBER_RESPONSE_NOKEY = ''' { "id": 24, "list_id": 42, - "email": "foo@example.org", + "email": "andy.example@example.org", "fingerprint": "", "admin": false, "delivery_enabled": true, @@ -96,15 +96,15 @@ _SUBSCRIBER_RESPONSE_NOKEY = ''' _KEY_RESPONSE = ''' { - "fingerprint": "2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6", - "email": "foo@example.org", + "fingerprint": "ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9", + "email": "andy.example@example.org", "expiry": null, - "generated_at": "2022-04-14T23:19:24.000Z", - "primary_uid": "Multischleuder Test Key (TEST - DO NOT USE) ", - "oneline": "0x2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6 foo@example.org 2022-04-14", + "generated_at": "2022-04-16T23:19:24.000Z", + "primary_uid": "Mutlischleuder Test User ", + "oneline": "0xADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9 andy.example@example.org 2022-04-16", "trust_issues": null, - "description": "pub ed25519 2022-04-14 [SC]\\n 2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6\\nuid Multischleuder Test Key (TEST - DO NOT USE) \\nsub cv25519 2022-04-14 [E]\\n", - "ascii": "pub ed25519 2022-04-14 [SC]\\n 2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6\\nuid Multischleuder Test Key (TEST - DO NOT USE) \\nsub cv25519 2022-04-14 [E]\\n-----BEGIN PGP PUBLIC KEY BLOCK-----\\n\\nmDMEYlirsBYJKwYBBAHaRw8BAQdAGAHsSb3b3x+V6d7XouOXJryqW4mcjn1nDT2z\\nFgf5lEy0PU11bHRpc2NobGV1ZGVyIFRlc3QgS2V5IChURVNUIC0gRE8gTk9UIFVT\\nRSkgPGZvb0BleGFtcGxlLm9yZz6IkAQTFggAOBYhBC+7wN+X/b8eS3BO7eOe9PrE\\nIL62BQJiWKuwAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEOOe9PrEIL62\\nUZUA/RrRiqhz+eY5PBXWvNzN2FjL0aTsrbPsjQ3fzQ0lThQkAQD+tVjLfx495OXn\\n/Y2NwnZaKtM80FPBsy2C0u0rEd6uALg4BGJYq7ASCisGAQQBl1UBBQEBB0A5xq5f\\nUb1i2Ayvbt+ZFgxx+OL3KT12AkSkLcaAeRjKcQMBCAeIeAQYFggAIBYhBC+7wN+X\\n/b8eS3BO7eOe9PrEIL62BQJiWKuwAhsMAAoJEOOe9PrEIL624lEA/iyn0KNUx8AK\\nrSMLp7JawmsT+uD2pcw1uH3qZPHMja3gAP91/1vKQ8X5tEYzlE9OvVqtf9ESQBLj\\nzxMaMEdub5qiBQ==\\n=RCkT\\n-----END PGP PUBLIC KEY BLOCK-----\\n" + "description": "pub 256?/ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9 2022-04-16\\nuid\\t\\tMutlischleuder Test User \\nsub 256?/ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9 2022-04-16\\nsub 256?/C0E8ED7A32F53626F2FCDC65F5035A1D90E35CAE 2022-04-16\\n", + "ascii": "pub 256?/ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9 2022-04-16\\nuid\\t\\tMutlischleuder Test User \\nsub 256?/ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9 2022-04-16\\nsub 256?/C0E8ED7A32F53626F2FCDC65F5035A1D90E35CAE 2022-04-16\\n\\n\\n-----BEGIN PGP PUBLIC KEY BLOCK-----\\n\\nmDMEYlsHSBYJKwYBBAHaRw8BAQdAhGNoFKTXFsAOR8xiC7WWDB4gv+TZq5tmPG7X\\n8C3h4my0SU11dGxpc2NobGV1ZGVyIFRlc3QgVXNlciAoVEVTVCBLRVkgRE8gTk9U\\nIFVTRSkgPGFuZHkuZXhhbXBsZUBleGFtcGxlLm9yZz6IkAQTFggAOBYhBK25vGef\\n9TzI72b6w5NI/at6dmP5BQJiWwdIAhsjBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheA\\nAAoJEJNI/at6dmP54NoBAMGktGRD7fgmruTviHhERbhUX9OmPGUuH1tsUFVAsePk\\nAP0Xt8Uq876t87FIMMil7zuo7Oc/lYqS+JONd0NEOIzUD7hXBGJbB0gSCSsGAQQB\\n2kcPAQIDBHhrny0kv/i58MlgJmR0g3dyadbPGt66Yht0dY6Azkz8eAbMuPG+Gqhu\\n/txLXnzPI1Gb99i934CCFUPgsvMorEIDAQgHiHgEGBYIACAWIQStubxnn/U8yO9m\\n+sOTSP2renZj+QUCYlsHSAIbDAAKCRCTSP2renZj+R9nAQDOcZRSgl9l7Z1inKjO\\nEwaQmYg/O9xked0C5mJwlV2mdgD9Gvamm5n6djU2D91X8Wbp49upWe1rAv2EgeAQ\\na5AcmwE=\\n=RIBQ\\n-----END PGP PUBLIC KEY BLOCK-----\\n\\n" } ''' # noqa E501 @@ -151,16 +151,16 @@ class TestSchleuderApi(unittest.TestCase): # Test request data self.assertEqual('https://localhost:4443/subscriptions.json?list_id=42', mock.call_args_list[0][0][0].get_full_url()) - self.assertEqual('https://localhost:4443/keys/2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6.json?list_id=42', + self.assertEqual('https://localhost:4443/keys/ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9.json?list_id=42', mock.call_args_list[1][0][0].get_full_url()) self.assertEqual(1, len(subs)) self.assertEqual(23, subs[0].id) - self.assertEqual('foo@example.org', subs[0].email) + self.assertEqual('andy.example@example.org', subs[0].email) self.assertEqual(42, subs[0].schleuder) self.assertEqual(datetime(2022, 4, 15, 1, 11, 12, 123000, tzinfo=tzutc()), subs[0].created_at) - self.assertEqual('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', subs[0].key.fingerprint) - self.assertEqual('foo@example.org', subs[0].key.email) + self.assertEqual('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', subs[0].key.fingerprint) + self.assertEqual('andy.example@example.org', subs[0].key.email) self.assertIn('-----BEGIN PGP PUBLIC KEY BLOCK-----', subs[0].key.blob) self.assertEqual(42, subs[0].key.schleuder) @@ -173,7 +173,7 @@ class TestSchleuderApi(unittest.TestCase): mock.call_args_list[0][0][0].get_full_url()) self.assertEqual(1, len(subs)) self.assertEqual(24, subs[0].id) - self.assertEqual('foo@example.org', subs[0].email) + self.assertEqual('andy.example@example.org', subs[0].email) self.assertEqual(42, subs[0].schleuder) self.assertEqual(datetime(2022, 4, 15, 1, 11, 12, 123000, tzinfo=tzutc()), subs[0].created_at) @@ -182,10 +182,10 @@ class TestSchleuderApi(unittest.TestCase): @patch('urllib.request.urlopen') def test_get_subscriber(self, mock): api = self._mock_api(mock) - sub = api.get_subscriber('foo@example.org', SchleuderList(42, '', '')) + sub = api.get_subscriber('andy.example@example.org', SchleuderList(42, '', '')) self.assertEqual(23, sub.id) - self.assertEqual('foo@example.org', sub.email) - self.assertEqual('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', sub.key.fingerprint) + self.assertEqual('andy.example@example.org', sub.email) + self.assertEqual('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', sub.key.fingerprint) self.assertEqual(42, sub.key.schleuder) self.assertEqual(42, sub.schleuder) with self.assertRaises(KeyError): @@ -194,17 +194,17 @@ class TestSchleuderApi(unittest.TestCase): @patch('urllib.request.urlopen') def test_get_subscriber_nokey(self, mock): api = self._mock_api(mock, nokey=True) - sub = api.get_subscriber('foo@example.org', SchleuderList(42, '', '')) + sub = api.get_subscriber('andy.example@example.org', SchleuderList(42, '', '')) self.assertEqual(24, sub.id) - self.assertEqual('foo@example.org', sub.email) + self.assertEqual('andy.example@example.org', sub.email) self.assertIsNone(sub.key) @patch('urllib.request.urlopen') def test_subscribe(self, mock): api = self._mock_api(mock) now = datetime.utcnow() - key = SchleuderKey('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', 'foo@example.org', 'verylongpgpkeyblock', 42) - sub = SchleuderSubscriber(23, 'foo@example.org', key, 42, now) + key = SchleuderKey('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', 'andy.example@example.org', 'verylongpgpkeyblock', 42) + sub = SchleuderSubscriber(23, 'andy.example@example.org', key, 42, now) api.subscribe(sub, SchleuderList(42, '', '')) self.assertEqual('https://localhost:4443/subscriptions.json?list_id=42', mock.call_args_list[-1][0][0].get_full_url()) @@ -215,7 +215,7 @@ class TestSchleuderApi(unittest.TestCase): def test_subscribe_nokey(self, mock): api = self._mock_api(mock) now = datetime.utcnow() - sub = SchleuderSubscriber(23, 'foo@example.org', None, 42, now) + sub = SchleuderSubscriber(23, 'andy.example@example.org', None, 42, now) with self.assertRaises(ValueError): api.subscribe(sub, SchleuderList(42, '', '')) @@ -223,8 +223,8 @@ class TestSchleuderApi(unittest.TestCase): def test_unsubscribe(self, mock): api = self._mock_api(mock) now = datetime.utcnow() - key = SchleuderKey('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', 'foo@example.org', 'verylongpgpkeyblock', 42) - sub = SchleuderSubscriber(23, 'foo@example.org', key, 42, now) + key = SchleuderKey('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', 'andy.example@example.org', 'verylongpgpkeyblock', 42) + sub = SchleuderSubscriber(23, 'andy.example@example.org', key, 42, now) api.unsubscribe(sub, SchleuderList(42, '', '')) self.assertEqual('https://localhost:4443/subscriptions/23.json', mock.call_args_list[-1][0][0].get_full_url()) @@ -235,8 +235,8 @@ class TestSchleuderApi(unittest.TestCase): def test_update_fingerprint(self, mock): api = self._mock_api(mock) now = datetime.utcnow() - key = SchleuderKey('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', 'foo@example.org', 'verylongpgpkeyblock', 42) - sub = SchleuderSubscriber(23, 'foo@example.org', key, 42, now) + key = SchleuderKey('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', 'andy.example@example.org', 'verylongpgpkeyblock', 42) + sub = SchleuderSubscriber(23, 'andy.example@example.org', key, 42, now) api.update_fingerprint(sub, SchleuderList(42, '', '')) self.assertEqual('https://localhost:4443/subscriptions/23.json', mock.call_args_list[-1][0][0].get_full_url()) @@ -247,25 +247,25 @@ class TestSchleuderApi(unittest.TestCase): def test_update_fingerprint_nokey(self, mock): api = self._mock_api(mock) now = datetime.utcnow() - sub = SchleuderSubscriber(23, 'foo@example.org', None, 42, now) + sub = SchleuderSubscriber(23, 'andy.example@example.org', None, 42, now) with self.assertRaises(ValueError): api.update_fingerprint(sub, SchleuderList(42, '', '')) @patch('urllib.request.urlopen') def test_get_key(self, mock): api = self._mock_api(mock) - key = api.get_key('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', SchleuderList(42, '', '')) - self.assertEqual('https://localhost:4443/keys/2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6.json?list_id=42', + key = api.get_key('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', SchleuderList(42, '', '')) + self.assertEqual('https://localhost:4443/keys/ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9.json?list_id=42', mock.call_args_list[0][0][0].get_full_url()) - self.assertEqual('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', key.fingerprint) - self.assertEqual('foo@example.org', key.email) + self.assertEqual('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', key.fingerprint) + self.assertEqual('andy.example@example.org', key.email) self.assertEqual(42, key.schleuder) self.assertIn('-----BEGIN PGP PUBLIC KEY BLOCK-----', key.blob) @patch('urllib.request.urlopen') def test_post_key(self, mock): api = self._mock_api(mock) - key = SchleuderKey('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', 'foo@example.org', 'verylongpgpkeyblock', 42) + key = SchleuderKey('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', 'andy.example@example.org', 'verylongpgpkeyblock', 42) api.post_key(key, SchleuderList(42, '', '')) self.assertEqual('https://localhost:4443/keys.json?list_id=42', mock.call_args_list[0][0][0].get_full_url()) @@ -275,9 +275,9 @@ class TestSchleuderApi(unittest.TestCase): @patch('urllib.request.urlopen') def test_delete_key(self, mock): api = self._mock_api(mock) - key = SchleuderKey('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', 'foo@example.org', 'verylongpgpkeyblock', 42) + key = SchleuderKey('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', 'andy.example@example.org', 'verylongpgpkeyblock', 42) api.delete_key(key, SchleuderList(42, '', '')) - self.assertEqual('https://localhost:4443/keys/2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6.json?list_id=42', + self.assertEqual('https://localhost:4443/keys/ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9.json?list_id=42', mock.call_args_list[0][0][0].get_full_url()) self.assertEqual('DELETE', mock.call_args_list[0][0][0].method) # todo assert request payload @@ -287,8 +287,8 @@ class TestSchleuderApi(unittest.TestCase): api = self._mock_api(mock) api.dry_run() now = datetime.utcnow() - key = SchleuderKey('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', 'foo@example.org', 'verylongpgpkeyblock', 42) - sub = SchleuderSubscriber(23, 'foo@example.org', key, 42, now) + key = SchleuderKey('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', 'andy.example@example.org', 'verylongpgpkeyblock', 42) + sub = SchleuderSubscriber(23, 'andy.example@example.org', key, 42, now) sch = SchleuderList(42, '', '') # create, update, delete should be no-ops; 5 requests for retrieving the keys & subscriptions in unsub & update api.subscribe(sub, sch) @@ -300,5 +300,5 @@ class TestSchleuderApi(unittest.TestCase): # only reads should execute api.get_lists() api.get_subscribers(sch) - api.get_key('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', sch) + api.get_key('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', sch) self.assertLess(2, len(mock.call_args_list)) diff --git a/multischleuder/test/test_types.py b/multischleuder/test/test_types.py index f86d8cc..5efc0c2 100644 --- a/multischleuder/test/test_types.py +++ b/multischleuder/test/test_types.py @@ -3,6 +3,7 @@ import datetime import json import unittest +import pgpy # type: ignore from dateutil.tz import tzutc from multischleuder.types import SchleuderKey, SchleuderList, SchleuderSubscriber @@ -19,29 +20,33 @@ class TestSchleuderTypes(unittest.TestCase): def test_parse_key(self): k = SchleuderKey.from_api(42, **json.loads(_KEY_RESPONSE)) - self.assertEqual('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', k.fingerprint) - self.assertEqual('foo@example.org', k.email) + self.assertEqual('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', k.fingerprint) + self.assertEqual('andy.example@example.org', k.email) self.assertEqual(42, k.schleuder) self.assertIn('-----BEGIN PGP PUBLIC KEY BLOCK-----', k.blob) - self.assertIn('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6 (foo@example.org)', str(k)) + self.assertIn('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9 (andy.example@example.org)', str(k)) + # Make sure the key can be used by PGPy + key, _ = pgpy.PGPKey.from_blob(k.blob) + msg = pgpy.PGPMessage.new('Hello World') + key.encrypt(msg) def test_parse_subscriber(self): k = SchleuderKey.from_api(42, **json.loads(_KEY_RESPONSE)) s = SchleuderSubscriber.from_api(k, **json.loads(_SUBSCRIBER_RESPONSE)[0]) self.assertEqual(23, s.id) - self.assertEqual('foo@example.org', str(s)) - self.assertEqual('foo@example.org', s.email) + self.assertEqual('andy.example@example.org', str(s)) + self.assertEqual('andy.example@example.org', s.email) self.assertEqual(42, s.schleuder) self.assertEqual(datetime.datetime(2022, 4, 15, 1, 11, 12, 123000, tzinfo=tzutc()), s.created_at) - self.assertEqual('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6', s.key.fingerprint) - self.assertIn('2FBBC0DF97FDBF1E4B704EEDE39EF4FAC420BEB6 (foo@example.org)', str(k)) + self.assertEqual('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9', s.key.fingerprint) + self.assertIn('ADB9BC679FF53CC8EF66FAC39348FDAB7A7663F9 (andy.example@example.org)', str(k)) def test_parse_subscriber_nokey(self): s = SchleuderSubscriber.from_api(None, **json.loads(_SUBSCRIBER_RESPONSE)[0]) self.assertEqual(23, s.id) - self.assertEqual('foo@example.org', str(s)) - self.assertEqual('foo@example.org', s.email) + self.assertEqual('andy.example@example.org', str(s)) + self.assertEqual('andy.example@example.org', s.email) self.assertEqual(42, s.schleuder) self.assertEqual(datetime.datetime(2022, 4, 15, 1, 11, 12, 123000, tzinfo=tzutc()), s.created_at) diff --git a/multischleuder/types.py b/multischleuder/types.py index 7c5bcb8..d033dd8 100644 --- a/multischleuder/types.py +++ b/multischleuder/types.py @@ -1,5 +1,5 @@ -from typing import Optional +from typing import List, Optional from dataclasses import dataclass, field, Field from datetime import datetime @@ -55,7 +55,16 @@ class SchleuderKey: email: str, ascii: str, *args, **kwargs) -> 'SchleuderKey': - return SchleuderKey(fingerprint, email, ascii, schleuder) + lines: List[str] = [] + state = 0 + for line in ascii.splitlines(): + if '-----BEGIN PGP ' in line: + state = 1 + if state == 1: + lines.append(line) + if '-----END PGP ' in line: + state = 1 + return SchleuderKey(fingerprint, email, '\n'.join(lines), schleuder) def __str__(self) -> str: return f'{self.fingerprint} ({self.email})' diff --git a/package/debian/multischleuder/etc/multischleuder/multischleuder.yml b/package/debian/multischleuder/etc/multischleuder/multischleuder.yml new file mode 100644 index 0000000..ee87393 --- /dev/null +++ b/package/debian/multischleuder/etc/multischleuder/multischleuder.yml @@ -0,0 +1,58 @@ +--- + +api: + url: "https://localhost:4443" + token: 2d039a8cfe414e55d1ec9ce9d4d787afc27050a6f630a024ae6c7dc5ab6941e5 + cafile: /etc/multischleuder/schleuder-ca.pem + +lists: + + - target: global@schleuder.example.org + unmanaged: + - admin@example.org + banned: + - banned@example.org + sources: + - east@schleuder.example.org + - west@schleuder.example.org + - north@schleuder.example.org + - south@schleuder.example.org + from: global-owner@schleuder.example.org + +smtp: + hostname: localhost + port: 8025 + +conflict: + interval: 604800 # 1 week + statefile: /var/lib/multischleuder/conflict.json + template: | + Hi {subscriber}, + + While compiling the subscriber list of {schleuder}, your + address {subscriber} was subscribed on multiple sub-lists with + different PGP keys. There may be something fishy or malicious going on, + or this may simply have been a mistake by you or a list admin. + + You have only been subscribed to {schleuder} using the key you + have been subscribed with for the *longest* time: + + {chosen} + + Please review the following keys and talk to the admins of the + corresponding sub-lists to resolve this issue: + + Fingerprint Sub-List + ----------- -------- + {affected} + + For your convenience, this message has been encrypted with *all* of the + above keys. If you have any questions, or do not understand this + message, please refer to your local Schleuder admin, or reply to this + message. + + Note that this automated message is unsigned, since MultiSchleuder does + not have access to Schleuder private keys. + + Regards + MultiSchleuder {schleuder} diff --git a/test/multischleuder.yml b/test/multischleuder.yml new file mode 100644 index 0000000..19a5b15 --- /dev/null +++ b/test/multischleuder.yml @@ -0,0 +1,70 @@ +--- + +api: + url: "https://localhost:4443" + token: "00000000000000000000000000000000" + cafile: /etc/schleuder/schleuder-certificate.pem + +lists: + + - target: test-global@schleuder.example.org + unmanaged: + - admin@example.org + - admin2@example.org + banned: + - aspammer@example.org + sources: + - test-north@schleuder.example.org + - test-east@schleuder.example.org + - test-south@schleuder.example.org + - test-west@schleuder.example.org + from: test-global-owner@schleuder.example.org + + - target: test2-global@schleuder.example.org + unmanaged: + - admin@example.org + banned: + - aspammer@example.org + - anotherspammer@example.org + sources: + - test-north@schleuder.example.org + - test-east@schleuder.example.org + from: test2-global-owner@schleuder.example.org + +smtp: + hostname: localhost + port: 25 + +conflict: + interval: 3600 # 1 hour - you don't want this in production + statefile: conflict.json + template: | + Hi {subscriber}, + + While compiling the subscriber list of {schleuder}, your + address {subscriber} was subscribed on multiple sub-lists with + different PGP keys. There may be something fishy or malicious going on, + or this may simply have been a mistake by you or a list admin. + + You have only been subscribed to {schleuder} using the key you + have been subscribed with for the *longest* time: + + {chosen} + + Please review the following keys and talk to the admins of the + corresponding sub-lists to resolve this issue: + + Fingerprint Sub-List + ----------- -------- + {affected} + + For your convenience, this message has been encrypted with *all* of the + above keys. If you have any questions, or do not understand this + message, please refer to your local Schleuder admin, or reply to this + message. + + Note that this automated message is unsigned, since MultiSchleuder does + not have access to Schleuder private keys. + + Regards + MultiSchleuder {schleuder} diff --git a/test/prepare-schleuder.sh b/test/prepare-schleuder.sh new file mode 100755 index 0000000..0037657 --- /dev/null +++ b/test/prepare-schleuder.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +function gen_key { + echo "gen_key $@" + PUID="${1}" + shift 1 + cat >/tmp/keygen <" + done + gpg --export --armor "${PUID}" > "/tmp/${PUID}.asc" + for uid in $@; do + gpg --export --armor "${uid}" > "/tmp/${uid}.asc" + done +} + +function subscribe { + schleuder-cli subscriptions new "${1}" "${2}" "/tmp/${2}.asc" +} + +gen_key admin@example.org +gen_key admin2@example.org +gen_key ada.lovelace@example.org +gen_key alex.example@example.org +gen_key aspammer@example.org +gen_key anna.example@example.org +mv /tmp/anna.example@example.org.asc /tmp/anna.example@example.org.old.asc + +gen_key anotherspammer@example.org +gen_key andy.example@example.org +mv /tmp/andy.example@example.org.asc /tmp/andy.example@example.org.1.asc +gen_key aaron.example@example.org aaron.example@example.net +gen_key amy.example@example.org + +install -m 0700 -d /tmp/gpg +export GNUPGHOME=/tmp/gpg +gen_key anna.example@example.org +gen_key andy.example@example.org +unset GNUPGHOME + +schleuder-cli lists new test@schleuder.example.org admin@example.org /tmp/admin@example.org.asc +schleuder-cli lists new test-global@schleuder.example.org admin@example.org /tmp/admin@example.org.asc +schleuder-cli lists new test-north@schleuder.example.org admin@example.org /tmp/admin@example.org.asc +schleuder-cli lists new test-east@schleuder.example.org admin@example.org /tmp/admin@example.org.asc +schleuder-cli lists new test-south@schleuder.example.org admin@example.org /tmp/admin@example.org.asc +schleuder-cli lists new test-west@schleuder.example.org admin2@example.org /tmp/admin2@example.org.asc +schleuder-cli lists new test2-global@schleuder.example.org admin2@example.org /tmp/admin2@example.org.asc + +subscribe test-global@schleuder.example.org ada.lovelace@example.org # should be unsubscribed +subscribe test-global@schleuder.example.org aaron.example@example.org # should remain as-is +subscribe test-global@schleuder.example.org aaron.example@example.net # should be unsubscribed, but key should remain +subscribe test-global@schleuder.example.org alex.example@example.org # should remain as-is +schleuder-cli subscriptions new test-global@schleuder.example.org anna.example@example.org /tmp/anna.example@example.org.old.asc + # key should be updated +subscribe test-global@schleuder.example.org aspammer@example.org # should be unsubscribed + +subscribe test-north@schleuder.example.org alex.example@example.org # should remain as-is +subscribe test-north@schleuder.example.org aspammer@example.org # should be ignored +schleuder-cli subscriptions new test-north@schleuder.example.org arno.example@example.org + # should not be subscribed - no key + +subscribe test-east@schleuder.example.org anna.example@example.org # key should be updated +subscribe test-east@schleuder.example.org anotherspammer@example.org # should not be subscribed +subscribe test-east@schleuder.example.org aaron.example@example.org # should remain as-is + +subscribe test-south@schleuder.example.org andy.example@example.org # should be subscribed despite key conflict +subscribe test-south@schleuder.example.org amy.example@example.org # should be subscribed - conflict but same key + +sleep 5 # to get different subscription dates + +schleuder-cli subscriptions new test-west@schleuder.example.org andy.example@example.org /tmp/andy.example@example.org.1.asc + # should not be subscribed +subscribe test-west@schleuder.example.org amy.example@example.org # should be subscribed - conflict but same key diff --git a/test/report.sh b/test/report.sh new file mode 100755 index 0000000..71dd9e7 --- /dev/null +++ b/test/report.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +echo Expected: +echo +echo aaron.example@example.org +echo admin@example.org +echo alex.example@example.org +echo amy.example@example.org +echo andy.example@example.org +echo anna.example@example.org +echo anotherspammer@example.org +echo -- --- +echo Actual: +echo +schleuder-cli subscriptions list test-global@schleuder.example.org + +schleuder-cli keys list test-global@schleuder.example.org + +cat /var/spool/mail/root