diff --git a/activitypub.py b/activitypub.py index 29fb8de..7a91aad 100644 --- a/activitypub.py +++ b/activitypub.py @@ -152,7 +152,7 @@ class ActivityPub(Protocol): logger.info(f'Headers: {json_dumps(dict(request.headers), indent=2)}') # parse_signature_header lower-cases all keys - keyId = parse_signature_header(sig).get('keyid') + keyId = fragmentless(parse_signature_header(sig).get('keyid')) if not keyId: error('HTTP Signature missing keyId', status=401) @@ -165,16 +165,21 @@ class ActivityPub(Protocol): error('Invalid Digest header, required for HTTP Signature', status=401) try: - key_actor = cls.get_object(keyId).as2 + key_actor = cls.get_object(keyId) except BadGateway: obj_id = as1.get_object(activity).get('id') if (activity.get('type') == 'Delete' and obj_id and - fragmentless(keyId) == fragmentless(obj_id)): - logging.info("Object/actor being deleted is also keyId; ignoring") - abort(202, 'OK') - raise + keyId == fragmentless(obj_id)): + logging.info('Object/actor being deleted is also keyId') + key_actor = Object(id=keyId, source_protocol='activitypub', deleted=True) + key_actor.put() + else: + raise - key = key_actor.get("publicKey", {}).get('publicKeyPem') + if key_actor.deleted: + abort(202, f'Ignoring, signer {keyId} is already deleted') + + key = key_actor.as2.get("publicKey", {}).get('publicKeyPem') logger.info(f'Verifying signature for {request.path} with key {key}') try: verified = HeaderVerifier(request.headers, key, diff --git a/protocol.py b/protocol.py index 312b91f..19fcaeb 100644 --- a/protocol.py +++ b/protocol.py @@ -386,7 +386,7 @@ class Protocol: id = util.fragmentless(id) logger.info(f'Loading Object {id}') obj = models.Object.get_by_id(id) - if obj and obj.as1: + if obj and (obj.as1 or obj.deleted): logger.info(' got from datastore') return obj diff --git a/tests/test_activitypub.py b/tests/test_activitypub.py index de2aa15..713f26d 100644 --- a/tests/test_activitypub.py +++ b/tests/test_activitypub.py @@ -234,7 +234,8 @@ class ActivityPubTest(testutil.TestCase): 'owner': 'http://own/er', 'publicKeyPem': self.user.public_pem().decode(), }, - }).put() + }) + self.key_id_obj.put() def assert_object(self, id, **props): return super().assert_object(id, delivered_protocol='webmention', **props) @@ -829,7 +830,7 @@ class ActivityPubTest(testutil.TestCase): def test_inbox_verify_http_signature(self, mock_common_log, mock_activitypub_log, _, mock_get, ___): # actor with a public key - self.key_id_obj.delete() + self.key_id_obj.key.delete() Protocol.get_object.cache.clear() mock_get.return_value = self.as2_resp({ **ACTOR, @@ -907,13 +908,23 @@ class ActivityPubTest(testutil.TestCase): self.assertEqual('active', other.key.get().status) def test_delete_actor_not_fetchable(self, _, mock_get, ___): - self.key_id_obj.delete() + self.key_id_obj.key.delete() Protocol.get_object.cache.clear() mock_get.return_value = requests_response(status=410) got = self.post('/inbox', json={**DELETE, 'object': 'http://my/key/id'}) self.assertEqual(202, got.status_code) + def test_delete_actor_empty_deleted_object(self, _, mock_get, ___): + self.key_id_obj.as2 = None + self.key_id_obj.deleted = True + self.key_id_obj.put() + Protocol.get_object.cache.clear() + + got = self.post('/inbox', json={**DELETE, 'object': 'http://my/key/id'}) + self.assertEqual(202, got.status_code) + mock_get.assert_not_called() + def test_delete_note(self, _, mock_get, ___): obj = Object(id='http://an/obj', as2={}) obj.put() diff --git a/tests/test_protocol.py b/tests/test_protocol.py index a05057a..621debd 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -71,3 +71,11 @@ class ProtocolTest(testutil.TestCase): stored.put() self.assert_entities_equal(stored, FakeProtocol.get_object('foo')) self.assertEqual([], FakeProtocol.fetched) + + @patch('requests.get') + def test_get_object_empty_deleted(self, mock_get): + stored = Object(id='foo', deleted=True) + stored.put() + + self.assert_entities_equal(stored, FakeProtocol.get_object('foo')) + mock_get.assert_not_called()