AP: always generate an actor for every outbound activity

for #279
pull/287/head
Ryan Barrett 2022-11-15 21:37:39 -08:00
rodzic e3f1431018
commit 46f039af63
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
4 zmienionych plików z 99 dodań i 25 usunięć

Wyświetl plik

@ -14,7 +14,7 @@ from oauth_dropins.webutil.flask_util import error
import requests
from werkzeug.exceptions import BadGateway
from models import Activity
from models import Activity, Domain
logger = logging.getLogger(__name__)
@ -242,22 +242,23 @@ def send_webmentions(activity_wrapped, proxy=None, **activity_props):
error(msg, status=int(errors[0][0] or 502))
def postprocess_as2(activity, target=None, domain=None):
def postprocess_as2(activity, domain=None, target=None):
"""Prepare an AS2 object to be served or sent via ActivityPub.
Args:
activity: dict, AS2 object or activity
domain: :class:`Domain`, required. populated into actor.id and
publicKey fields if needed.
target: dict, AS2 object, optional. The target of activity's inReplyTo or
Like/Announce/etc object, if any.
domain: :class:`models.Domain`, optional. populated into publicKey field
if provided.
"""
assert domain
type = activity.get('type')
# actor objects
if type == 'Person':
postprocess_as2_actor(activity)
if not activity.get('publicKey') and domain:
postprocess_as2_actor(activity, domain)
if not activity.get('publicKey'):
# underspecified, inferred from this issue and Mastodon's implementation:
# https://github.com/w3c/activitypub/issues/203#issuecomment-297553229
# https://github.com/tootsuite/mastodon/blob/bc2c263504e584e154384ecc2d804aeb1afb1ba3/app/services/activitypub/process_account_service.rb#L77
@ -275,7 +276,7 @@ def postprocess_as2(activity, target=None, domain=None):
for actor in (util.get_list(activity, 'attributedTo') +
util.get_list(activity, 'actor')):
postprocess_as2_actor(actor)
postprocess_as2_actor(actor, domain)
# inReplyTo: singly valued, prefer id over url
target_id = target.get('id') if target else None
@ -344,7 +345,9 @@ def postprocess_as2(activity, target=None, domain=None):
# to public, since Mastodon interprets to public as public, cc public as unlisted:
# https://socialhub.activitypub.rocks/t/visibility-to-cc-mapping/284
# https://wordsmith.social/falkreon/securing-activitypub
activity.setdefault('to', []).append(AS2_PUBLIC_AUDIENCE)
to = activity.setdefault('to', [])
if AS2_PUBLIC_AUDIENCE not in to:
to.append(AS2_PUBLIC_AUDIENCE)
# wrap articles and notes in a Create activity
if type in ('Article', 'Note'):
@ -352,29 +355,37 @@ def postprocess_as2(activity, target=None, domain=None):
'@context': as2.CONTEXT,
'type': 'Create',
'id': f'{activity["id"]}#bridgy-fed-create',
'actor': postprocess_as2_actor({}, domain),
'object': activity,
}
return util.trim_nulls(activity)
def postprocess_as2_actor(actor):
def postprocess_as2_actor(actor, domain=None):
"""Prepare an AS2 actor object to be served or sent via ActivityPub.
Modifies actor in place.
Args:
actor: dict, AS2 actor object
domain: :class:`Domain`
Returns:
actor dict
"""
url = actor.get('url')
if url:
domain = urllib.parse.urlparse(url).netloc
actor.update({
'id': request.host_url + domain,
'url': redirect_wrap(url),
'preferredUsername': domain,
})
url = actor.get('url') or f'https://{domain.key.id()}/'
domain_str = urllib.parse.urlparse(url).netloc
actor.setdefault('id', request.host_url + domain_str)
actor.update({
'url': redirect_wrap(url),
'preferredUsername': domain_str,
})
# required by pixelfed. https://github.com/snarfed/bridgy-fed/issues/39
actor.setdefault('summary', '')
return actor
def redirect_wrap(url):

Wyświetl plik

@ -51,9 +51,11 @@ def redir(to):
util.domain_from_link(to, minimize=False),
urllib.parse.urlparse(to).hostname))
for domain in domains:
if domain and Domain.get_by_id(domain):
logger.info(f'Found Domain for domain {domain}')
break
if domain:
entity = Domain.get_by_id(domain)
if entity:
logger.info(f'Found Domain for domain {domain}')
break
else:
logger.info(f'No user found for any of {domains}; returning 404')
abort(404)
@ -62,24 +64,29 @@ def redir(to):
# priorities.
if request.headers.get('Accept') in (common.CONTENT_TYPE_AS2,
common.CONTENT_TYPE_AS2_LD):
return convert_to_as2(to)
return convert_to_as2(to, entity)
# redirect
logger.info(f'redirecting to {to}')
return redirect(to, code=301)
def convert_to_as2(url):
def convert_to_as2(url, domain):
"""Fetch a URL as HTML, convert it to AS2, and return it.
Currently mainly for Pixelfed.
https://github.com/snarfed/bridgy-fed/issues/39
Args:
url: str
domain: :class:`Domain`
"""
mf2 = util.fetch_mf2(url)
entry = mf2util.find_first_entry(mf2, ['h-entry'])
logger.info(f"Parsed mf2 for {mf2['url']}: {json_dumps(entry, indent=2)}")
obj = common.postprocess_as2(as2.from_as1(microformats2.json_to_object(entry)))
obj = common.postprocess_as2(as2.from_as1(microformats2.json_to_object(entry)),
domain)
logger.info(f'Returning: {json_dumps(obj, indent=2)}')
return obj, {

Wyświetl plik

@ -9,6 +9,7 @@ from werkzeug.exceptions import BadGateway
from app import app
import common
from models import Domain
from . import testutil
HTML = requests_response('<html></html>', headers={
@ -67,11 +68,56 @@ class CommonTest(testutil.TestCase):
def test_postprocess_as2_multiple_in_reply_tos(self):
with app.test_request_context('/'):
self.assertEqual({
self.assert_equals({
'id': 'http://localhost/r/xyz',
'inReplyTo': 'foo',
'to': [common.AS2_PUBLIC_AUDIENCE],
}, common.postprocess_as2({
'id': 'xyz',
'inReplyTo': ['foo', 'bar'],
}))
}, domain=Domain(id='foo.com')))
def test_postprocess_as2_actor_attributedTo(self):
with app.test_request_context('/'):
self.assert_equals({
'actor': {
'id': 'baj',
'preferredUsername': 'foo.com',
'url': 'http://localhost/r/https://foo.com/',
},
'attributedTo': [{
'id': 'bar',
'preferredUsername': 'foo.com',
'url': 'http://localhost/r/https://foo.com/',
}, {
'id': 'baz',
'preferredUsername': 'foo.com',
'url': 'http://localhost/r/https://foo.com/',
}],
'to': [common.AS2_PUBLIC_AUDIENCE],
}, common.postprocess_as2({
'attributedTo': [{'id': 'bar'}, {'id': 'baz'}],
'actor': {'id': 'baj'},
}, domain=Domain(id='foo.com')))
def test_postprocess_as2_note(self):
with app.test_request_context('/'):
self.assert_equals({
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'http://localhost/r/xyz#bridgy-fed-create',
'type': 'Create',
'actor': {
'id': 'http://localhost/foo.com',
'url': 'http://localhost/r/https://foo.com/',
'preferredUsername': 'foo.com'
},
'object': {
'id': 'http://localhost/r/xyz',
'type': 'Note',
'to': [common.AS2_PUBLIC_AUDIENCE],
},
}, common.postprocess_as2({
'id': 'xyz',
'type': 'Note',
}, domain=Domain(id='foo.com')))

Wyświetl plik

@ -158,6 +158,11 @@ class WebmentionTest(testutil.TestCase):
'@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Create',
'id': 'http://localhost/r/http://a/reply#bridgy-fed-create',
'actor': {
'id': 'http://localhost/a',
'url': 'http://localhost/r/https://a/',
'preferredUsername': 'a',
},
'object': {
'@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Note',
@ -240,6 +245,11 @@ class WebmentionTest(testutil.TestCase):
'@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Create',
'id': 'http://localhost/r/http://orig/post#bridgy-fed-create',
'actor': {
'id': 'http://localhost/orig',
'url': 'http://localhost/r/https://orig/',
'preferredUsername': 'orig',
},
'object': {
'@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Note',