kopia lustrzana https://gitlab.com/marnanel/chapeau
Intermediate checkin
rodzic
1432d3b83f
commit
e342c828f2
|
@ -5,11 +5,7 @@ __author__ = 'Marnanel Thurman'
|
|||
__license__ = 'GPL-2'
|
||||
__copyright__ = 'Copyright (c) 2018 Marnanel Thurman'
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(name='django_kepi')
|
||||
|
||||
ATSIGN_CONTEXT = "https://www.w3.org/ns/activitystreams",
|
||||
ATSIGN_CONTEXT = "https://www.w3.org/ns/activitystreams"
|
||||
|
||||
PUBLIC_IDS = set([
|
||||
'https://www.w3.org/ns/activitystreams#Public',
|
||||
|
|
|
@ -11,7 +11,7 @@ to their audiences.
|
|||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
from celery import shared_task
|
||||
from django_kepi.find import find, find_local
|
||||
from django_kepi.find import find, find_local, is_local
|
||||
import django_kepi.models
|
||||
from httpsig.verify import HeaderVerifier
|
||||
from urllib.parse import urlparse
|
||||
|
@ -103,6 +103,12 @@ def _recipients_to_inboxes(recipients):
|
|||
logger.debug(' -- ignoring public')
|
||||
continue
|
||||
|
||||
if is_local(recipient):
|
||||
logger.debug(' -- %s is local; use directly',
|
||||
recipient)
|
||||
inboxes.add(recipient)
|
||||
continue
|
||||
|
||||
discovered = find(recipient)
|
||||
|
||||
if discovered is None:
|
||||
|
@ -179,19 +185,22 @@ def _recipients_to_inboxes(recipients):
|
|||
|
||||
return inboxes
|
||||
|
||||
def _activity_form_to_outgoing_string(activity_form,
|
||||
local_actor = None):
|
||||
def _activity_form_to_outgoing_string(activity_form):
|
||||
"""
|
||||
Formats an activity ready to be sent out as
|
||||
an HTTP response.
|
||||
"""
|
||||
# XXX local_actor can be removed
|
||||
|
||||
from django_kepi import ATSIGN_CONTEXT
|
||||
|
||||
format_for_delivery = activity_form.copy()
|
||||
for blind_field in ['bto', 'bcc']:
|
||||
if blind_field in format_for_delivery:
|
||||
del format_for_delivery[blind_field]
|
||||
|
||||
if '@context' not in format_for_delivery:
|
||||
format_for_delivery['@context'] = ATSIGN_CONTEXT
|
||||
|
||||
message = json.dumps(
|
||||
format_for_delivery,
|
||||
sort_keys = True,
|
||||
|
@ -386,7 +395,8 @@ def deliver(
|
|||
|
||||
# Actors don't get told about their own activities
|
||||
if not incoming:
|
||||
if activity_form['actor'] in recipients:
|
||||
if 'actor' in activity_form and \
|
||||
activity_form['actor'] in recipients:
|
||||
recipients.remove(activity_form['actor'])
|
||||
|
||||
if not recipients:
|
||||
|
@ -419,7 +429,6 @@ def deliver(
|
|||
|
||||
message = _activity_form_to_outgoing_string(
|
||||
activity_form = activity_form,
|
||||
local_actor = local_actor,
|
||||
)
|
||||
|
||||
signer = _signer_for_local_actor(
|
||||
|
|
|
@ -7,6 +7,8 @@ logger = logging.getLogger(name='django_kepi')
|
|||
|
||||
class Activity(thing.Object):
|
||||
|
||||
_explicit_object_field = False
|
||||
|
||||
def go_into_outbox_if_local(self):
|
||||
|
||||
from django_kepi.models.collection import Collection
|
||||
|
@ -28,14 +30,13 @@ class Activity(thing.Object):
|
|||
local_only=True,
|
||||
object_to_store=self)
|
||||
|
||||
##############################
|
||||
|
||||
class Create(Activity):
|
||||
|
||||
@property
|
||||
def activity_form(self):
|
||||
result = super().activity_form
|
||||
|
||||
if not self._explicit_object_field:
|
||||
return result
|
||||
|
||||
our_object = self['object__obj']
|
||||
|
||||
if our_object is None:
|
||||
|
@ -46,6 +47,11 @@ class Create(Activity):
|
|||
|
||||
return result
|
||||
|
||||
##############################
|
||||
|
||||
class Create(Activity):
|
||||
_explicit_object_field = True
|
||||
|
||||
class Update(Activity):
|
||||
pass
|
||||
|
||||
|
@ -53,7 +59,7 @@ class Delete(Activity):
|
|||
pass
|
||||
|
||||
class Follow(Activity):
|
||||
pass
|
||||
_explicit_object_field = True
|
||||
|
||||
class Add(Activity):
|
||||
pass
|
||||
|
|
|
@ -236,7 +236,9 @@ def create(activity):
|
|||
creation = kepi_create(
|
||||
value = raw_material,
|
||||
is_local_user = activity.is_local,
|
||||
run_side_effects = False)
|
||||
run_side_effects = False,
|
||||
run_delivery = False,
|
||||
)
|
||||
activity['object'] = creation
|
||||
activity.save()
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ import re
|
|||
from django.conf import settings
|
||||
from urllib.parse import urlparse
|
||||
import django_kepi.find
|
||||
import django_kepi.create
|
||||
import django.core.exceptions
|
||||
from httpsig.verify import HeaderVerifier
|
||||
|
||||
|
@ -159,6 +158,7 @@ def _run_validation(
|
|||
"""
|
||||
|
||||
from django_kepi.delivery import deliver
|
||||
from django_kepi.create import create
|
||||
|
||||
logger.info('%s: begin validation',
|
||||
message_id)
|
||||
|
@ -252,10 +252,12 @@ def _run_validation(
|
|||
|
||||
logger.debug('%s: validation passed!', message)
|
||||
|
||||
result = django_kepi.create.create(
|
||||
result = create(
|
||||
sender=actor,
|
||||
is_local_user = message.is_local_user,
|
||||
**(message.activity_form),
|
||||
run_delivery = True,
|
||||
incoming = True,
|
||||
)
|
||||
|
||||
if result is None:
|
||||
|
@ -264,7 +266,4 @@ def _run_validation(
|
|||
|
||||
logger.info('%s: produced new Thing %s', message, result)
|
||||
|
||||
deliver(result.number,
|
||||
incoming = True)
|
||||
|
||||
return result
|
||||
|
|
|
@ -135,16 +135,20 @@ class KepiView(django.views.View):
|
|||
By default, KepiViews call self.activity_get() to get
|
||||
the data to render.
|
||||
"""
|
||||
logger.info('800')
|
||||
result = self.activity_get(request, *args, **kwargs)
|
||||
|
||||
logger.info('801')
|
||||
if result is None:
|
||||
raise Http404()
|
||||
|
||||
logger.info('802')
|
||||
if isinstance(result, HttpResponse):
|
||||
logger.info('self.activity_get() returned HttpResponse %s',
|
||||
result)
|
||||
return result
|
||||
|
||||
logger.info('803')
|
||||
return self._render_object(request, result)
|
||||
|
||||
def _to_json(self, data):
|
||||
|
@ -350,7 +354,7 @@ class FollowingView(KepiView):
|
|||
remote_url = None,
|
||||
)
|
||||
|
||||
logging.debug('Finding followers of %s: %s',
|
||||
logger.debug('Finding followers of %s: %s',
|
||||
kwargs['username'], person)
|
||||
|
||||
return Following.objects.filter(follower=person.url,
|
||||
|
@ -368,14 +372,17 @@ class FollowersView(KepiView):
|
|||
def activity_get(self, request, *args, **kwargs):
|
||||
|
||||
logger.debug('Finding followers of %s:', kwargs['username'])
|
||||
logger.info('701')
|
||||
|
||||
person = Actor.objects.get(
|
||||
f_preferredUsername=kwargs['username'],
|
||||
remote_url = None,
|
||||
)
|
||||
|
||||
logging.debug('Finding followers of %s: %s',
|
||||
logger.info('702')
|
||||
logger.debug('Finding followers of %s: %s',
|
||||
kwargs['username'], person)
|
||||
logger.info('709')
|
||||
|
||||
return Following.objects.filter(following=person.url,
|
||||
pending=False)
|
||||
|
|
|
@ -4,20 +4,24 @@ from django_kepi.create import create
|
|||
from . import create_local_person, REMOTE_FRED, REMOTE_JIM
|
||||
|
||||
class TestAudience(TestCase):
|
||||
|
||||
def test_add_audiences_for(self):
|
||||
narcissus = create_local_person(
|
||||
name = 'narcissus',
|
||||
)
|
||||
|
||||
like = create(
|
||||
def setUp(self):
|
||||
self._narcissus = create_local_person(
|
||||
name = 'self._narcissus',
|
||||
)
|
||||
|
||||
def test_add_audiences_for(self):
|
||||
|
||||
self._like = create(
|
||||
f_type = 'Like',
|
||||
f_actor = narcissus,
|
||||
f_object = narcissus,
|
||||
f_actor = self._narcissus,
|
||||
f_object = self._narcissus,
|
||||
run_side_effects = False,
|
||||
run_delivery = False,
|
||||
)
|
||||
|
||||
a = Audience.add_audiences_for(
|
||||
thing = like,
|
||||
thing = self._like,
|
||||
field = 'to',
|
||||
value = [
|
||||
REMOTE_FRED,
|
||||
|
@ -26,7 +30,7 @@ class TestAudience(TestCase):
|
|||
)
|
||||
|
||||
results = Audience.objects.filter(
|
||||
parent = like,
|
||||
parent = self._like,
|
||||
)
|
||||
|
||||
self.assertEqual(len(results), 2)
|
||||
|
@ -34,19 +38,18 @@ class TestAudience(TestCase):
|
|||
self.assertEqual(results[1].recipient, REMOTE_JIM)
|
||||
|
||||
def test_create(self):
|
||||
narcissus = create_local_person(
|
||||
name = 'narcissus',
|
||||
)
|
||||
|
||||
like = create(
|
||||
self._like = create(
|
||||
f_type = 'Like',
|
||||
f_actor = narcissus,
|
||||
f_object = narcissus,
|
||||
f_actor = self._narcissus,
|
||||
f_object = self._narcissus,
|
||||
to = [ REMOTE_FRED, REMOTE_JIM, ],
|
||||
run_side_effects = False,
|
||||
run_delivery = False,
|
||||
)
|
||||
|
||||
results = Audience.objects.filter(
|
||||
parent = like,
|
||||
parent = self._like,
|
||||
)
|
||||
|
||||
self.assertEqual(len(results), 2)
|
||||
|
@ -54,19 +57,18 @@ class TestAudience(TestCase):
|
|||
self.assertEqual(results[1].recipient, REMOTE_JIM)
|
||||
|
||||
def test_get_audiences_for(self):
|
||||
narcissus = create_local_person(
|
||||
name = 'narcissus',
|
||||
)
|
||||
|
||||
like = create(
|
||||
self._like = create(
|
||||
f_type = 'Like',
|
||||
f_actor = narcissus,
|
||||
f_object = narcissus,
|
||||
f_actor = self._narcissus,
|
||||
f_object = self._narcissus,
|
||||
run_side_effects = False,
|
||||
run_delivery = False,
|
||||
)
|
||||
|
||||
for fieldname in ['to', 'cc', 'bcc']:
|
||||
a = Audience.add_audiences_for(
|
||||
thing = like,
|
||||
thing = self._like,
|
||||
field = fieldname,
|
||||
value = [
|
||||
REMOTE_FRED,
|
||||
|
@ -75,7 +77,7 @@ class TestAudience(TestCase):
|
|||
)
|
||||
|
||||
self.assertDictEqual(
|
||||
Audience.get_audiences_for(like),
|
||||
Audience.get_audiences_for(self._like),
|
||||
{'to': ['https://remote.example.org/users/fred',
|
||||
'https://remote.example.org/users/jim'],
|
||||
'cc': ['https://remote.example.org/users/fred',
|
||||
|
@ -85,7 +87,7 @@ class TestAudience(TestCase):
|
|||
})
|
||||
|
||||
self.assertDictEqual(
|
||||
Audience.get_audiences_for(like,
|
||||
Audience.get_audiences_for(self._like,
|
||||
hide_blind = True,
|
||||
),
|
||||
{'to': ['https://remote.example.org/users/fred',
|
||||
|
@ -93,6 +95,3 @@ class TestAudience(TestCase):
|
|||
'cc': ['https://remote.example.org/users/fred',
|
||||
'https://remote.example.org/users/jim'],
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -3,15 +3,17 @@ from unittest import skip
|
|||
from django_kepi.models import *
|
||||
import datetime
|
||||
import json
|
||||
from django_kepi import logger
|
||||
from django_kepi.find import find
|
||||
from . import *
|
||||
import logging
|
||||
|
||||
EXAMPLE_SERVER = 'http://testserver'
|
||||
JSON_TYPE = 'application/activity+json'
|
||||
PAGE_LENGTH = 50
|
||||
|
||||
class CollectionTests(TestCase):
|
||||
logger = logging.getLogger(name='django_kepi')
|
||||
|
||||
class TestCollection(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
settings.KEPI['LOCAL_OBJECT_HOSTNAME'] = 'testserver'
|
||||
|
@ -21,19 +23,25 @@ class CollectionTests(TestCase):
|
|||
path,
|
||||
expectedTotalItems):
|
||||
|
||||
logger.info('361')
|
||||
c = Client(
|
||||
HTTP_ACCEPT = JSON_TYPE,
|
||||
)
|
||||
|
||||
logger.info('362 %s', path)
|
||||
response = c.get(path)
|
||||
|
||||
logger.info('363 %s', path)
|
||||
if response.status_code!=200:
|
||||
raise RuntimeError(response.content)
|
||||
|
||||
logger.info('364')
|
||||
self.assertEqual(response['Content-Type'], JSON_TYPE)
|
||||
|
||||
logger.info('365')
|
||||
result = json.loads(response.content.decode(encoding='UTF-8'))
|
||||
|
||||
logger.info('366')
|
||||
for field in [
|
||||
'@context',
|
||||
'id',
|
||||
|
@ -41,6 +49,7 @@ class CollectionTests(TestCase):
|
|||
'type',
|
||||
]:
|
||||
self.assertIn(field, result)
|
||||
logger.info('367')
|
||||
|
||||
if expectedTotalItems==0:
|
||||
self.assertNotIn('first', result)
|
||||
|
@ -48,9 +57,11 @@ class CollectionTests(TestCase):
|
|||
self.assertIn('first', result)
|
||||
self.assertEqual(result['first'], EXAMPLE_SERVER+path+'?page=1')
|
||||
|
||||
logger.info('368')
|
||||
self.assertEqual(result['id'], EXAMPLE_SERVER+path)
|
||||
self.assertEqual(result['totalItems'], expectedTotalItems)
|
||||
self.assertEqual(result['type'], 'OrderedCollection')
|
||||
logger.info('369')
|
||||
|
||||
def check_collection_page(self,
|
||||
path,
|
||||
|
@ -150,6 +161,7 @@ class CollectionTests(TestCase):
|
|||
people = {}
|
||||
|
||||
for name in ['alice', 'bob', 'carol']:
|
||||
logger.info('3')
|
||||
people[name] = create_local_person(name = name)
|
||||
|
||||
follow = create(
|
||||
|
@ -163,14 +175,17 @@ class CollectionTests(TestCase):
|
|||
actor = people['alice'],
|
||||
f_object = follow,
|
||||
)
|
||||
logger.info('37')
|
||||
|
||||
path = '/users/alice/followers'
|
||||
|
||||
logger.info('38')
|
||||
self.check_collection(
|
||||
path=path,
|
||||
expectedTotalItems=3,
|
||||
)
|
||||
|
||||
logger.info('39')
|
||||
self.check_collection_page(
|
||||
path=path,
|
||||
page_number=1,
|
||||
|
@ -182,6 +197,7 @@ class CollectionTests(TestCase):
|
|||
],
|
||||
)
|
||||
|
||||
logger.info('4')
|
||||
for name in ['alice', 'bob', 'carol']:
|
||||
|
||||
path='/users/{}/following'.format(name)
|
||||
|
@ -199,5 +215,3 @@ class CollectionTests(TestCase):
|
|||
'https://testserver/users/alice',
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -37,97 +37,6 @@ def _message_became_activity(url=ACTIVITY_ID):
|
|||
except Object.DoesNotExist:
|
||||
return False
|
||||
|
||||
class TestDeliverTasks(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
settings.KEPI['LOCAL_OBJECT_HOSTNAME'] = 'testserver'
|
||||
|
||||
def _run_delivery(
|
||||
self,
|
||||
activity_fields,
|
||||
remote_user_details,
|
||||
remote_user_endpoints,
|
||||
):
|
||||
|
||||
a = create(value=activity_fields)
|
||||
a.save()
|
||||
|
||||
for who, what in remote_user_details.items():
|
||||
mock_remote_object(
|
||||
url = who,
|
||||
content = json.dumps(what),
|
||||
)
|
||||
|
||||
for who in remote_user_endpoints:
|
||||
mock_remote_object(
|
||||
url = who,
|
||||
content = 'Thank you',
|
||||
as_post = True,
|
||||
)
|
||||
|
||||
deliver(a.number)
|
||||
|
||||
@httpretty.activate
|
||||
def test_deliver_remote(self):
|
||||
|
||||
keys = json.load(open('tests/keys/keys-0000.json', 'r'))
|
||||
|
||||
alice = create_local_person(
|
||||
name = 'alice',
|
||||
publicKey = keys['public'],
|
||||
privateKey = keys['private'],
|
||||
)
|
||||
|
||||
self._run_delivery(
|
||||
activity_fields = {
|
||||
'type': 'Follow',
|
||||
'actor': LOCAL_ALICE,
|
||||
'object': REMOTE_FRED,
|
||||
'to': [REMOTE_FRED],
|
||||
},
|
||||
remote_user_details = {
|
||||
REMOTE_FRED: remote_user(
|
||||
url=REMOTE_FRED,
|
||||
name='Fred',
|
||||
sharedInbox=REMOTE_SHARED_INBOX,
|
||||
),
|
||||
},
|
||||
remote_user_endpoints = [
|
||||
REMOTE_SHARED_INBOX,
|
||||
],
|
||||
)
|
||||
|
||||
@httpretty.activate
|
||||
def test_deliver_local(self):
|
||||
|
||||
keys0 = json.load(open('tests/keys/keys-0000.json', 'r'))
|
||||
keys1 = json.load(open('tests/keys/keys-0001.json', 'r'))
|
||||
alice = create_local_person(
|
||||
name = 'alice',
|
||||
publicKey = keys0['public'],
|
||||
privateKey = keys0['private'],
|
||||
)
|
||||
bob = create_local_person(
|
||||
name = 'bob',
|
||||
publicKey = keys1['public'],
|
||||
privateKey = keys1['private'],
|
||||
)
|
||||
|
||||
self._run_delivery(
|
||||
activity_fields = {
|
||||
'type': 'Follow',
|
||||
'actor': LOCAL_ALICE,
|
||||
'object': LOCAL_BOB,
|
||||
'to': [LOCAL_BOB],
|
||||
},
|
||||
remote_user_details = {
|
||||
},
|
||||
remote_user_endpoints = [
|
||||
],
|
||||
)
|
||||
|
||||
# FIXME add some assertions!
|
||||
|
||||
class TestDelivery(TestCase):
|
||||
|
||||
def _set_up_remote_user_mocks(self):
|
||||
|
@ -187,7 +96,7 @@ class TestDelivery(TestCase):
|
|||
privateKey = keys['private'])
|
||||
create_local_person(name='bob')
|
||||
|
||||
@patch.object(django_kepi.views.activitypub.InboxView, 'activity_store')
|
||||
@patch.object(django_kepi.views.activitypub.ActorView, 'activity_store')
|
||||
@httpretty.activate
|
||||
def _test_delivery(self,
|
||||
fake_local_request,
|
||||
|
@ -221,8 +130,6 @@ class TestDelivery(TestCase):
|
|||
)
|
||||
like.save()
|
||||
|
||||
deliver(like.number)
|
||||
|
||||
#################
|
||||
# Assertions
|
||||
|
||||
|
@ -233,16 +140,11 @@ class TestDelivery(TestCase):
|
|||
REMOTE_PATH_NAMES.get(req.path, req.path),
|
||||
)
|
||||
|
||||
if fake_local_request.call_args:
|
||||
for req in fake_local_request.call_args:
|
||||
try:
|
||||
path = req[0].path
|
||||
touched.append(path)
|
||||
except KeyError:
|
||||
pass
|
||||
for obj, kwargs in fake_local_request.call_args_list:
|
||||
touched.append(kwargs['username'])
|
||||
|
||||
logger.info('Inboxes touched: %s', touched)
|
||||
logger.info(' " " expected: %s', expected)
|
||||
logger.info('Inboxes touched: %s', sorted(touched))
|
||||
logger.info('Inboxes expected: %s', sorted(expected))
|
||||
|
||||
self.assertListEqual(
|
||||
sorted(touched),
|
||||
|
@ -252,13 +154,13 @@ class TestDelivery(TestCase):
|
|||
def test_simple_remote_and_local(self):
|
||||
self._test_delivery(
|
||||
to=[REMOTE_FRED, LOCAL_BOB],
|
||||
expected=['fred', '/sharedInbox'],
|
||||
expected=['fred', 'bob'],
|
||||
)
|
||||
|
||||
def test_simple_local(self):
|
||||
self._test_delivery(
|
||||
to=[LOCAL_BOB],
|
||||
expected=['/sharedInbox'],
|
||||
expected=['bob'],
|
||||
)
|
||||
|
||||
def test_simple_remote(self):
|
||||
|
@ -294,5 +196,5 @@ class TestDelivery(TestCase):
|
|||
def test_remote_followers(self):
|
||||
self._test_delivery(
|
||||
to=[REMOTE_FRED, FREDS_FOLLOWERS],
|
||||
expected=['fred', 'jim', '/sharedInbox'],
|
||||
expected=['fred', 'jim', 'alice', 'bob'],
|
||||
)
|
||||
|
|
|
@ -67,7 +67,6 @@ BOOST = {
|
|||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"cc": [
|
||||
"https://weirder.earth.example.net/users/natrix",
|
||||
"https://altair.example.com/users/alice/followers"
|
||||
],
|
||||
"object": "https://weirder.earth.example.net/users/natrix/statuses/102504233649665656",
|
||||
|
|
|
@ -32,11 +32,8 @@ OBJECT_FORM = {
|
|||
"type": "Note",
|
||||
'attributedTo': ALICE_ID,
|
||||
"name": "Chris liked 'Minimal ActivityPub update client'",
|
||||
"object": "https://rhiaro.co.uk/2016/05/minimal-activitypub",
|
||||
"to": ["https://rhiaro.co.uk/#amy",
|
||||
"https://dustycloud.org/followers",
|
||||
"https://rhiaro.co.uk/followers/"],
|
||||
"cc": "https://e14n.com/evan"
|
||||
"object": "https://example.net/2016/05/minimal-activitypub",
|
||||
"to": [PUBLIC],
|
||||
}
|
||||
|
||||
CREATE_FORM = {
|
||||
|
|
Ładowanie…
Reference in New Issue