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

Wyświetl plik

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

Wyświetl plik

@ -9,6 +9,7 @@ from werkzeug.exceptions import BadGateway
from app import app from app import app
import common import common
from models import Domain
from . import testutil from . import testutil
HTML = requests_response('<html></html>', headers={ HTML = requests_response('<html></html>', headers={
@ -67,11 +68,56 @@ class CommonTest(testutil.TestCase):
def test_postprocess_as2_multiple_in_reply_tos(self): def test_postprocess_as2_multiple_in_reply_tos(self):
with app.test_request_context('/'): with app.test_request_context('/'):
self.assertEqual({ self.assert_equals({
'id': 'http://localhost/r/xyz', 'id': 'http://localhost/r/xyz',
'inReplyTo': 'foo', 'inReplyTo': 'foo',
'to': [common.AS2_PUBLIC_AUDIENCE], 'to': [common.AS2_PUBLIC_AUDIENCE],
}, common.postprocess_as2({ }, common.postprocess_as2({
'id': 'xyz', 'id': 'xyz',
'inReplyTo': ['foo', 'bar'], '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', '@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Create', 'type': 'Create',
'id': 'http://localhost/r/http://a/reply#bridgy-fed-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': { 'object': {
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Note', 'type': 'Note',
@ -240,6 +245,11 @@ class WebmentionTest(testutil.TestCase):
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Create', 'type': 'Create',
'id': 'http://localhost/r/http://orig/post#bridgy-fed-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': { 'object': {
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Note', 'type': 'Note',