kopia lustrzana https://github.com/snarfed/bridgy-fed
rodzic
5f4d6757e7
commit
9c62786f06
|
@ -474,6 +474,7 @@ class Protocol:
|
|||
|
||||
# fetch actor if necessary so we have name, profile photo, etc
|
||||
if actor and actor.keys() == set(['id']):
|
||||
logger.info('Fetching actor so we have name, profile photo, etc')
|
||||
actor_obj = cls.load(actor['id'])
|
||||
if actor_obj.as1:
|
||||
obj.our_as1 = {**obj.as1, 'actor': actor_obj.as1}
|
||||
|
@ -481,6 +482,7 @@ class Protocol:
|
|||
# fetch object if necessary so we can render it in feeds
|
||||
if obj.type == 'share' and inner_obj_as1.keys() == set(['id']):
|
||||
if not inner_obj and cls.owns_id(inner_obj_id):
|
||||
logger.info('Fetching object so we can render it in feeds')
|
||||
inner_obj = cls.load(inner_obj_id)
|
||||
if inner_obj and inner_obj.as1:
|
||||
obj.our_as1 = {
|
||||
|
@ -673,7 +675,7 @@ class Protocol:
|
|||
sent = protocol.send(obj, target.uri, log_data=log_data)
|
||||
if sent:
|
||||
add(obj.delivered, target)
|
||||
obj.undelivered.remove(target)
|
||||
obj.undelivered.remove(target)
|
||||
except BaseException as e:
|
||||
code, body = util.interpret_http_exception(e)
|
||||
if not code and not body:
|
||||
|
|
|
@ -276,6 +276,8 @@ class ActivityPubTest(TestCase):
|
|||
|
||||
self.user = self.make_user('user.com', has_hcard=True, has_redirects=True,
|
||||
obj_as2={**ACTOR, 'id': 'https://user.com/'})
|
||||
self.swentel_key = ndb.Key(ActivityPub, 'https://mas.to/users/swentel')
|
||||
self.user_actor_key = ndb.Key(ActivityPub, 'https://user.com/actor')
|
||||
|
||||
ACTOR_BASE['publicKey']['publicKeyPem'] = self.user.public_pem().decode()
|
||||
|
||||
|
@ -396,7 +398,8 @@ class ActivityPubTest(TestCase):
|
|||
self._test_inbox_reply(reply, {
|
||||
'as2': reply,
|
||||
'type': 'post',
|
||||
'labels': ['activity', 'notification'],
|
||||
'labels': ['activity', 'user', 'notification'],
|
||||
'users': [self.user.key, self.user_actor_key],
|
||||
}, mock_head, mock_get, mock_post)
|
||||
|
||||
self.assert_user(ActivityPub, 'https://user.com/actor',
|
||||
|
@ -427,7 +430,7 @@ class ActivityPubTest(TestCase):
|
|||
{'as2': REPLY,
|
||||
'type': 'post',
|
||||
'object_ids': [REPLY_OBJECT['id']],
|
||||
'labels': ['notification', 'activity'],
|
||||
'labels': ['notification', 'activity', 'user'],
|
||||
},
|
||||
mock_head, mock_get, mock_post)
|
||||
self.assert_object(REPLY_OBJECT['id'],
|
||||
|
@ -435,7 +438,8 @@ class ActivityPubTest(TestCase):
|
|||
our_as1=as2.to_as1(REPLY_OBJECT),
|
||||
type='comment')
|
||||
|
||||
def _test_inbox_reply(self, reply, expected_props, mock_head, mock_get, mock_post):
|
||||
def _test_inbox_reply(self, reply, expected_props, mock_head, mock_get,
|
||||
mock_post):
|
||||
mock_head.return_value = requests_response(url='https://user.com/post')
|
||||
mock_get.side_effect = (
|
||||
(list(mock_get.side_effect) if mock_get.side_effect else [])
|
||||
|
@ -461,12 +465,14 @@ class ActivityPubTest(TestCase):
|
|||
},
|
||||
)
|
||||
|
||||
self.assert_object(reply['id'],
|
||||
users=[self.user.key],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
delivered=['https://user.com/post'],
|
||||
**expected_props)
|
||||
expected_props = {
|
||||
'users': [self.user.key, self.swentel_key],
|
||||
'source_protocol': 'activitypub',
|
||||
'status': 'complete',
|
||||
'delivered': ['https://user.com/post'],
|
||||
**expected_props,
|
||||
}
|
||||
self.assert_object(reply['id'], **expected_props)
|
||||
|
||||
def test_inbox_reply_to_self_domain(self, *mocks):
|
||||
self._test_inbox_ignore_reply_to('http://localhost/mas.to', *mocks)
|
||||
|
@ -480,14 +486,15 @@ class ActivityPubTest(TestCase):
|
|||
|
||||
mock_head.return_value = requests_response(url='http://mas.to/')
|
||||
mock_get.side_effect = [
|
||||
# actor fetch
|
||||
self.as2_resp(ACTOR),
|
||||
# protocol inference
|
||||
requests_response(test_web.NOTE_HTML),
|
||||
requests_response(test_web.NOTE_HTML),
|
||||
]
|
||||
|
||||
got = self.post('/user.com/inbox', json=reply)
|
||||
self.assertEqual(200, got.status_code, got.get_data(as_text=True))
|
||||
|
||||
self.assertEqual(204, got.status_code, got.get_data(as_text=True))
|
||||
mock_post.assert_not_called()
|
||||
|
||||
def test_individual_inbox_create_obj(self, *mocks):
|
||||
|
@ -566,24 +573,25 @@ class ActivityPubTest(TestCase):
|
|||
},
|
||||
)
|
||||
|
||||
repost['object'] = note
|
||||
del repost['object']['to']
|
||||
del repost['object']['cc']
|
||||
self.assert_object(REPOST_FULL['id'],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
our_as1=as2.to_as1(repost),
|
||||
users=[self.user.key],
|
||||
as2={
|
||||
**REPOST,
|
||||
'actor': ACTOR,
|
||||
'object': orig_url,
|
||||
},
|
||||
users=[self.swentel_key],
|
||||
delivered=['https://user.com/orig'],
|
||||
type='share',
|
||||
labels=['activity', 'feed', 'notification'],
|
||||
labels=['activity', 'user', 'feed'],
|
||||
object_ids=['https://user.com/orig'])
|
||||
|
||||
def test_shared_inbox_repost_of_fediverse(self, mock_head, mock_get, mock_post):
|
||||
Follower.get_or_create(to=ActivityPub.get_or_create(ACTOR['id']),
|
||||
from_=self.user)
|
||||
Follower.get_or_create(to=ActivityPub.get_or_create(ACTOR['id']),
|
||||
from_=Fake.get_or_create('http://baz'))
|
||||
baz = Fake.get_or_create('http://baz')
|
||||
Follower.get_or_create(to=ActivityPub.get_or_create(ACTOR['id']), from_=baz)
|
||||
Follower.get_or_create(to=ActivityPub.get_or_create(ACTOR['id']),
|
||||
from_=Fake.get_or_create('http://baj'),
|
||||
status='inactive')
|
||||
|
@ -599,7 +607,7 @@ class ActivityPubTest(TestCase):
|
|||
]
|
||||
|
||||
got = self.post('/ap/sharedInbox', json=REPOST)
|
||||
self.assertEqual(200, got.status_code, got.get_data(as_text=True))
|
||||
self.assertEqual(204, got.status_code, got.get_data(as_text=True))
|
||||
|
||||
mock_post.assert_not_called() # no webmention
|
||||
|
||||
|
@ -607,7 +615,7 @@ class ActivityPubTest(TestCase):
|
|||
source_protocol='activitypub',
|
||||
status='ignored',
|
||||
our_as1=as2.to_as1(REPOST_FULL),
|
||||
users=[self.user.key, Fake(id='http://baz').key],
|
||||
users=[self.user.key, baz, self.swentel_key],
|
||||
type='share',
|
||||
labels=['activity', 'feed'],
|
||||
object_ids=[REPOST['object']])
|
||||
|
@ -623,8 +631,11 @@ class ActivityPubTest(TestCase):
|
|||
HTML,
|
||||
]
|
||||
|
||||
got = self.post('/ap/sharedInbox', json={**LIKE, 'object': 'http://nope.com/post'})
|
||||
self.assertEqual(200, got.status_code)
|
||||
got = self.post('/ap/sharedInbox', json={
|
||||
**LIKE,
|
||||
'object': 'http://nope.com/post',
|
||||
})
|
||||
self.assertEqual(204, got.status_code)
|
||||
|
||||
self.assert_object('http://mas.to/like#ok',
|
||||
# no nope.com Web user key since it didn't exist
|
||||
|
@ -744,7 +755,7 @@ class ActivityPubTest(TestCase):
|
|||
}, kwargs['data'])
|
||||
|
||||
self.assert_object('http://mas.to/like#ok',
|
||||
users=[self.user.key],
|
||||
users=[self.user.key, self.user_actor_key],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
our_as1=as2.to_as1(LIKE_WITH_ACTOR),
|
||||
|
@ -764,14 +775,14 @@ class ActivityPubTest(TestCase):
|
|||
obj_as2=LIKE_ACTOR, direct=True)
|
||||
|
||||
def test_inbox_follow_accept_with_id(self, *mocks):
|
||||
self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT, *mocks)
|
||||
self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT, 200, *mocks)
|
||||
|
||||
follow = {
|
||||
**FOLLOW_WITH_ACTOR,
|
||||
'url': 'https://mas.to/users/swentel#followed-https://user.com/',
|
||||
}
|
||||
self.assert_object('https://mas.to/6d1a',
|
||||
users=[self.user.key],
|
||||
users=[self.user.key, self.swentel_key],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
our_as1=as2.to_as1(follow),
|
||||
|
@ -788,14 +799,14 @@ class ActivityPubTest(TestCase):
|
|||
'url': FOLLOW['object'],
|
||||
},
|
||||
}
|
||||
self._test_inbox_follow_accept(follow, ACCEPT, *mocks)
|
||||
self._test_inbox_follow_accept(follow, ACCEPT, 200, *mocks)
|
||||
|
||||
follow.update({
|
||||
'actor': ACTOR,
|
||||
'url': 'https://mas.to/users/swentel#followed-https://user.com/',
|
||||
})
|
||||
self.assert_object('https://mas.to/6d1a',
|
||||
users=[self.user.key],
|
||||
users=[self.user.key, self.swentel_key],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
our_as1=as2.to_as1(follow),
|
||||
|
@ -804,29 +815,28 @@ class ActivityPubTest(TestCase):
|
|||
labels=['notification', 'activity'],
|
||||
object_ids=[FOLLOW['object']])
|
||||
|
||||
def test_inbox_follow_accept_webmention_fails(self, mock_head, mock_get, mock_post):
|
||||
def test_inbox_follow_accept_webmention_fails(self, mock_head, mock_get,
|
||||
mock_post):
|
||||
mock_post.side_effect = [
|
||||
requests_response(), # AP Accept
|
||||
requests.ConnectionError(), # webmention
|
||||
]
|
||||
self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT,
|
||||
self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT, 304,
|
||||
mock_head, mock_get, mock_post)
|
||||
|
||||
follow = {
|
||||
**FOLLOW_WITH_ACTOR,
|
||||
'url': 'https://mas.to/users/swentel#followed-https://user.com/',
|
||||
}
|
||||
url = 'https://mas.to/users/swentel#followed-https://user.com/'
|
||||
self.assert_object('https://mas.to/6d1a',
|
||||
users=[self.user.key],
|
||||
users=[self.user.key, self.swentel_key],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
our_as1=as2.to_as1(follow),
|
||||
status='failed',
|
||||
our_as1=as2.to_as1({**FOLLOW_WITH_ACTOR, 'url': url}),
|
||||
delivered=[],
|
||||
failed=['https://user.com/'],
|
||||
type='follow',
|
||||
labels=['notification', 'activity'],
|
||||
labels=['notification', 'activity', 'user'],
|
||||
object_ids=[FOLLOW['object']])
|
||||
|
||||
def _test_inbox_follow_accept(self, follow_as2, accept_as2,
|
||||
def _test_inbox_follow_accept(self, follow_as2, accept_as2, expected_status,
|
||||
mock_head, mock_get, mock_post):
|
||||
# this should makes us make the follower ActivityPub as direct=True
|
||||
self.user.direct = False
|
||||
|
@ -842,7 +852,7 @@ class ActivityPubTest(TestCase):
|
|||
mock_post.return_value = requests_response()
|
||||
|
||||
got = self.post('/user.com/inbox', json=follow_as2)
|
||||
self.assertEqual(200, got.status_code)
|
||||
self.assertEqual(expected_status, got.status_code)
|
||||
|
||||
mock_get.assert_has_calls((
|
||||
self.as2_req(FOLLOW['actor']),
|
||||
|
@ -889,7 +899,7 @@ class ActivityPubTest(TestCase):
|
|||
mock_post.return_value = requests_response()
|
||||
|
||||
got = self.post('/user.com/inbox', json=FOLLOW_WRAPPED)
|
||||
self.assertEqual(200, got.status_code)
|
||||
self.assertEqual(204, got.status_code)
|
||||
|
||||
follower = Follower.query().get()
|
||||
self.assert_entities_equal(
|
||||
|
@ -994,23 +1004,29 @@ class ActivityPubTest(TestCase):
|
|||
|
||||
id = 'https://mas.to/users/tmichellemoore#likes/56486252'
|
||||
bad_url = 'http://localhost/r/Testing \u2013 Brid.gy \u2013 Post to Mastodon 3'
|
||||
got = self.post('/user.com/inbox', json={
|
||||
bad = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
'id': id,
|
||||
'type': 'Like',
|
||||
'actor': ACTOR['id'],
|
||||
'object': bad_url,
|
||||
})
|
||||
}
|
||||
got = self.post('/user.com/inbox', json=bad)
|
||||
|
||||
# bad object, should ignore activity
|
||||
self.assertEqual(200, got.status_code)
|
||||
self.assertEqual(204, got.status_code)
|
||||
mock_post.assert_not_called()
|
||||
|
||||
obj = Object.get_by_id(id)
|
||||
self.assertEqual(['activity'], obj.labels)
|
||||
self.assertEqual([], obj.users)
|
||||
self.assertEqual([], obj.domains)
|
||||
|
||||
self.assert_object(id,
|
||||
our_as1={
|
||||
**as2.to_as1(bad),
|
||||
'actor': as2.to_as1(ACTOR),
|
||||
},
|
||||
labels=['activity', 'user'],
|
||||
users=[self.swentel_key],
|
||||
source_protocol='activitypub',
|
||||
status='ignored',
|
||||
)
|
||||
self.assertIsNone(Object.get_by_id(bad_url))
|
||||
|
||||
@patch('activitypub.logger.info', side_effect=logging.info)
|
||||
|
@ -1034,7 +1050,7 @@ class ActivityPubTest(TestCase):
|
|||
body = json_dumps(NOTE)
|
||||
headers = self.sign('/ap/sharedInbox', json_dumps(NOTE))
|
||||
resp = self.client.post('/ap/sharedInbox', data=body, headers=headers)
|
||||
self.assertEqual(200, resp.status_code, resp.get_data(as_text=True))
|
||||
self.assertEqual(204, resp.status_code, resp.get_data(as_text=True))
|
||||
mock_get.assert_has_calls((
|
||||
self.as2_req('http://my/key/id'),
|
||||
))
|
||||
|
@ -1133,7 +1149,6 @@ class ActivityPubTest(TestCase):
|
|||
self.assertEqual(204, resp.status_code)
|
||||
self.assertTrue(obj.key.get().deleted)
|
||||
self.assert_object(delete['id'],
|
||||
as2=delete,
|
||||
our_as1={
|
||||
**as2.to_as1(delete),
|
||||
'actor': as2.to_as1(ACTOR),
|
||||
|
@ -1162,21 +1177,29 @@ class ActivityPubTest(TestCase):
|
|||
]
|
||||
|
||||
resp = self.post('/ap/sharedInbox', json=UPDATE_NOTE)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(204, resp.status_code)
|
||||
|
||||
note_as1 = as2.to_as1({
|
||||
**UPDATE_NOTE['object'],
|
||||
'author': {'id': 'https://mas.to/users/swentel'},
|
||||
})
|
||||
self.assert_object('https://a/note',
|
||||
type='note',
|
||||
our_as1=as2.to_as1({
|
||||
**UPDATE_NOTE['object'],
|
||||
'author': {'id': 'https://mas.to/users/swentel'},
|
||||
}),
|
||||
our_as1=note_as1,
|
||||
source_protocol='activitypub')
|
||||
|
||||
update_as1 = {
|
||||
**as2.to_as1(UPDATE_NOTE),
|
||||
'object': note_as1,
|
||||
'actor': as2.to_as1(ACTOR),
|
||||
}
|
||||
self.assert_object(UPDATE_NOTE['id'],
|
||||
source_protocol='activitypub',
|
||||
type='update',
|
||||
status='complete',
|
||||
as2=UPDATE_NOTE,
|
||||
labels=['activity'])
|
||||
status='ignored',
|
||||
our_as1=update_as1,
|
||||
labels=['activity', 'user'],
|
||||
users=[self.swentel_key])
|
||||
|
||||
self.assert_entities_equal(Object.get_by_id('https://a/note'),
|
||||
protocol.objects_cache['https://a/note'])
|
||||
|
@ -1208,22 +1231,22 @@ class ActivityPubTest(TestCase):
|
|||
]
|
||||
|
||||
got = self.post('/user.com/inbox', json=LIKE)
|
||||
self.assertEqual(200, got.status_code)
|
||||
self.assertEqual(204, got.status_code)
|
||||
|
||||
self.assert_object('http://mas.to/like#ok',
|
||||
users=[self.user.key],
|
||||
users=[self.user.key, self.user_actor_key],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
status='ignored',
|
||||
our_as1=as2.to_as1(LIKE_WITH_ACTOR),
|
||||
type='like',
|
||||
labels=['activity', 'notification'],
|
||||
labels=['activity', 'user', 'notification'],
|
||||
object_ids=[LIKE['object']])
|
||||
|
||||
def test_inbox_id_already_seen(self, *mocks):
|
||||
obj_key = Object(id=FOLLOW_WRAPPED['id'], as2={}).put()
|
||||
|
||||
got = self.post('/user.com/inbox', json=FOLLOW_WRAPPED)
|
||||
self.assertEqual(200, got.status_code)
|
||||
self.assertEqual(204, got.status_code)
|
||||
self.assertEqual(0, Follower.query().count())
|
||||
|
||||
# second time should use in memory cache
|
||||
|
|
|
@ -279,13 +279,7 @@ class ProtocolReceiveTest(TestCase):
|
|||
def assert_object(self, id, **props):
|
||||
props.setdefault('source_protocol', 'fake')
|
||||
props.setdefault('delivered_protocol', 'fake')
|
||||
|
||||
ignore = []
|
||||
for field in 'as2', 'bsky', 'mf2':
|
||||
if 'our_as1' in props and field not in props:
|
||||
ignore.append(field)
|
||||
|
||||
return super().assert_object(id, ignore=ignore, **props)
|
||||
return super().assert_object(id, **props)
|
||||
|
||||
def make_followers(self):
|
||||
Follower.get_or_create(to=self.user, from_=self.alice)
|
||||
|
|
|
@ -300,6 +300,7 @@ class TestCase(unittest.TestCase, testutil.Asserts):
|
|||
mock.assert_any_call(url, **kwargs)
|
||||
|
||||
def assert_object(self, id, delivered_protocol=None, **props):
|
||||
ignore = props.pop('ignore', [])
|
||||
got = Object.get_by_id(id)
|
||||
assert got, id
|
||||
|
||||
|
@ -308,6 +309,12 @@ class TestCase(unittest.TestCase, testutil.Asserts):
|
|||
props[field] = [Target(uri=uri, protocol=delivered_protocol)
|
||||
for uri in props.get(field, [])]
|
||||
|
||||
if 'our_as1' in props:
|
||||
assert 'as2' not in props
|
||||
assert 'bsky' not in props
|
||||
assert 'mf2' not in props
|
||||
ignore.extend(['as2', 'bsky', 'mf2'])
|
||||
|
||||
mf2 = props.get('mf2')
|
||||
if mf2 and 'items' in mf2:
|
||||
props['mf2'] = mf2['items'][0]
|
||||
|
@ -329,7 +336,6 @@ class TestCase(unittest.TestCase, testutil.Asserts):
|
|||
for target in got.delivered:
|
||||
del target.key
|
||||
|
||||
ignore = props.pop('ignore', [])
|
||||
self.assert_entities_equal(Object(id=id, **props), got,
|
||||
ignore=['as1', 'created', 'expire',
|
||||
'object_ids', 'type', 'updated'
|
||||
|
|
15
web.py
15
web.py
|
@ -270,23 +270,20 @@ class Web(User, Protocol):
|
|||
|
||||
See :meth:`Protocol.send` for details.
|
||||
|
||||
*Does not* propagate HTTP errors, DNS or connection failures, or other
|
||||
exceptions, since webmention support is optional for web recipients.
|
||||
Returns true if the target URL doesn't advertise a webmention endpoint,
|
||||
since webmention support itself is optional for web recipients.
|
||||
https://fed.brid.gy/docs#error-handling
|
||||
"""
|
||||
source_url = obj.proxy_url()
|
||||
logger.info(f'Sending webmention from {source_url} to {url}')
|
||||
|
||||
endpoint = common.webmention_discover(url).endpoint
|
||||
try:
|
||||
if endpoint:
|
||||
webmention.send(endpoint, source_url, url)
|
||||
return True
|
||||
except RequestException as e:
|
||||
# log exception, then ignore it
|
||||
util.interpret_http_exception(e)
|
||||
if not endpoint:
|
||||
return False
|
||||
|
||||
webmention.send(endpoint, source_url, url)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def fetch(cls, obj, gateway=False, check_backlink=False, **kwargs):
|
||||
"""Fetches a URL over HTTP and extracts its microformats2.
|
||||
|
|
Ładowanie…
Reference in New Issue