diff --git a/django_kepi/find.py b/django_kepi/find.py index 110d3cf..a6a585a 100644 --- a/django_kepi/find.py +++ b/django_kepi/find.py @@ -140,6 +140,14 @@ class ThingRequest(HttpRequest): self.method = 'ACTIVITY' +class TombstoneException(Exception): + def __init__(self, tombstone, *args, **kwargs): + self.tombstone = tombstone + super().__init__(*args, **kwargs) + + def __str__(self): + return self.tombstone.__str__() + def find_local(path): try: @@ -160,6 +168,9 @@ def find_local(path): **resolved.kwargs) logger.debug('%s: resulting in %s', path, str(result)) + if result.f_type == 'Tombstone': + raise TombstoneException(result) + return result def find_remote(url): diff --git a/django_kepi/models/thing.py b/django_kepi/models/thing.py index 643bd72..9d3d7bd 100644 --- a/django_kepi/models/thing.py +++ b/django_kepi/models/thing.py @@ -1,6 +1,6 @@ from django.db import models, IntegrityError from django.conf import settings -from django_kepi.find import find +from django_kepi.find import find, is_local from django_kepi.delivery import deliver import django_kepi.models.following import logging @@ -211,6 +211,39 @@ class Thing(models.Model): following = self['actor'], ) + @property + def is_local(self): + if not self.remote_url: + return True + + return is_local(self.remote_url) + + def entomb(self): + logger.info('%s: entombing', self) + + if self.f_type=='Tombstone': + logger.warn(' -- already entombed; ignoring') + return + + if not self.is_local: + raise ValueError("%s: you can't entomb remote things %s", + self, str(self.remote_url)) + + former_type = self.f_type + + self.f_type = 'Tombstone' + self.active = True + + ThingField.objects.filter(parent=self).delete() + + for f,v in [ + ('former_type', former_type), + # XXX 'deleted', when we're doing timestamps + ]: + ThingField(parent=self, name=f, value=json.dumps(v)).save() + + self.save() + logger.info('%s: entombing finished', self) TYPES = { # actor object target @@ -335,8 +368,6 @@ class Thing(models.Model): record_fields['f_type'] = value['type'] if 'id' in value: - # FIXME this allows people to create "remote" Activities - # with local URLs, which is weird and shouldn't happen. record_fields['remote_url'] = value['id'] for f in ['id', 'type', 'actor', 'name']: diff --git a/django_kepi/views.py b/django_kepi/views.py index bbcef4f..0623e2e 100644 --- a/django_kepi/views.py +++ b/django_kepi/views.py @@ -97,6 +97,10 @@ class KepiView(django.views.View): result['Content-Type'] = 'application/activity+json' + if data['type']=='Tombstone': + result.reason = 'Entombed' + result.status_code = 410 + return result def _collection_get(self, request, items): @@ -252,6 +256,9 @@ class ActorView(ThingView): def activity(self, request, *args, **kwargs): thing = super().activity(request, *args, **kwargs) + if thing.f_type=='Tombstone': + return thing + logger.debug(' -- found Thing %s; does it have an Actor?', thing) diff --git a/tests/test_views.py b/tests/test_views.py index 0adae3d..f0779df 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -78,3 +78,36 @@ class TestKepiView(TestCase): } ) +class TestTombstone(TestCase): + + def test_tombstone(self): + + queen_anne = create_local_person('queen_anne') + + c = Client() + response = c.get('/users/queen_anne') + + self.assertEqual(response.status_code, 200) + self.assertDictEqual( + _response_to_dict(response), + { + 'name': 'queen_anne', + 'id': 'https://altair.example.com/users/queen_anne', + 'type': 'Person', + }, + ) + + queen_anne.entomb() + + response = c.get('/users/queen_anne') + + self.assertEqual(response.status_code, 410) + self.assertDictEqual( + _response_to_dict(response), + { + 'id': 'https://altair.example.com/users/queen_anne', + 'type': 'Tombstone', + 'former_type': 'Person', + 'name': 'queen_anne', + }, + )