kopia lustrzana https://github.com/snarfed/bridgy-fed
drop User.as2, Object.as_as2, switch callers to Protocol.convert
rodzic
3471476092
commit
ca8b7484c0
|
@ -93,7 +93,7 @@ class ActivityPub(User, Protocol):
|
||||||
def handle(self):
|
def handle(self):
|
||||||
"""Returns this user's ActivityPub address, eg ``@user@foo.com``."""
|
"""Returns this user's ActivityPub address, eg ``@user@foo.com``."""
|
||||||
if self.obj and self.obj.as1:
|
if self.obj and self.obj.as1:
|
||||||
addr = as2.address(self.as2())
|
addr = as2.address(self.convert(self.obj))
|
||||||
if addr:
|
if addr:
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ class ActivityPub(User, Protocol):
|
||||||
|
|
||||||
logger.info(f'{obj.key} type {obj.type} is not an actor and has no author or actor with inbox')
|
logger.info(f'{obj.key} type {obj.type} is not an actor and has no author or actor with inbox')
|
||||||
|
|
||||||
actor = obj.as_as2()
|
actor = ActivityPub.convert(obj)
|
||||||
|
|
||||||
if shared:
|
if shared:
|
||||||
shared_inbox = actor.get('endpoints', {}).get('sharedInbox')
|
shared_inbox = actor.get('endpoints', {}).get('sharedInbox')
|
||||||
|
@ -199,9 +199,7 @@ class ActivityPub(User, Protocol):
|
||||||
logger.info(f'Skipping sending to blocklisted {url}')
|
logger.info(f'Skipping sending to blocklisted {url}')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
orig_as2 = orig_obj.as_as2() if orig_obj else None
|
activity = to_cls.convert(obj, orig_obj=to_cls.convert(orig_obj))
|
||||||
activity = obj.as2 or postprocess_as2(obj.as_as2(), orig_obj=orig_as2)
|
|
||||||
|
|
||||||
if not activity.get('actor'):
|
if not activity.get('actor'):
|
||||||
logger.warning('Outgoing AP activity has no actor!')
|
logger.warning('Outgoing AP activity has no actor!')
|
||||||
|
|
||||||
|
@ -318,7 +316,7 @@ class ActivityPub(User, Protocol):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert(cls, obj):
|
def convert(cls, obj, **kwargs):
|
||||||
"""Convert a :class:`models.Object` to AS2.
|
"""Convert a :class:`models.Object` to AS2.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -326,8 +324,17 @@ class ActivityPub(User, Protocol):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: AS2 JSON
|
dict: AS2 JSON
|
||||||
|
kwargs: passed through to :func:`postprocess_as2`
|
||||||
"""
|
"""
|
||||||
return postprocess_as2(as2.from_as1(obj.as1))
|
if not obj:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if obj.as2:
|
||||||
|
return obj.as2
|
||||||
|
elif obj.source_protocol in ('ap', 'activitypub'):
|
||||||
|
return as2.from_as1(obj.as1)
|
||||||
|
|
||||||
|
return postprocess_as2(as2.from_as1(obj.as1), **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def verify_signature(cls, activity):
|
def verify_signature(cls, activity):
|
||||||
|
@ -399,7 +406,8 @@ class ActivityPub(User, Protocol):
|
||||||
elif not key_actor or not key_actor.as1:
|
elif not key_actor or not key_actor.as1:
|
||||||
error(f"Couldn't load {keyId} to verify signature", status=401)
|
error(f"Couldn't load {keyId} to verify signature", status=401)
|
||||||
|
|
||||||
key = key_actor.as_as2().get('publicKey', {}).get('publicKeyPem')
|
# don't ActivityPub.convert since we don't want to postprocess_as2
|
||||||
|
key = as2.from_as1(key_actor.as1).get('publicKey', {}).get('publicKeyPem')
|
||||||
if not key:
|
if not key:
|
||||||
error(f'No public key for {keyId}', status=401)
|
error(f'No public key for {keyId}', status=401)
|
||||||
|
|
||||||
|
@ -524,8 +532,9 @@ def postprocess_as2(activity, orig_obj=None, wrap=True):
|
||||||
"""
|
"""
|
||||||
if not activity or isinstance(activity, str):
|
if not activity or isinstance(activity, str):
|
||||||
return activity
|
return activity
|
||||||
|
elif activity.keys() == {'id'}:
|
||||||
|
return activity['id']
|
||||||
|
|
||||||
assert g.user
|
|
||||||
type = activity.get('type')
|
type = activity.get('type')
|
||||||
|
|
||||||
# actor objects
|
# actor objects
|
||||||
|
@ -681,10 +690,12 @@ def postprocess_as2(activity, orig_obj=None, wrap=True):
|
||||||
if content := obj_or_activity.get('content'):
|
if content := obj_or_activity.get('content'):
|
||||||
obj_or_activity.setdefault('contentMap', {'en': content})
|
obj_or_activity.setdefault('contentMap', {'en': content})
|
||||||
|
|
||||||
activity['object'] = postprocess_as2(
|
activity['object'] = [
|
||||||
activity.get('object'),
|
postprocess_as2(o, orig_obj=orig_obj,
|
||||||
orig_obj=orig_obj,
|
wrap=wrap and type in ('Create', 'Update', 'Delete'))
|
||||||
wrap=wrap and type in ('Create', 'Update', 'Delete'))
|
for o in as1.get_objects(activity)]
|
||||||
|
if len(activity['object']) == 1:
|
||||||
|
activity['object'] = activity['object'][0]
|
||||||
|
|
||||||
return util.trim_nulls(activity)
|
return util.trim_nulls(activity)
|
||||||
|
|
||||||
|
@ -712,28 +723,26 @@ def postprocess_as2_actor(actor, wrap=True):
|
||||||
urls = util.get_list(actor, 'url')
|
urls = util.get_list(actor, 'url')
|
||||||
if not urls and url:
|
if not urls and url:
|
||||||
urls = [url]
|
urls = [url]
|
||||||
|
if urls and wrap:
|
||||||
domain = util.domain_from_link(urls[0], minimize=False)
|
|
||||||
if wrap:
|
|
||||||
urls[0] = redirect_wrap(urls[0])
|
urls[0] = redirect_wrap(urls[0])
|
||||||
|
|
||||||
id = actor.get('id')
|
id = actor.get('id')
|
||||||
if g.user and (not id or g.user.is_web_url(id)):
|
if g.user and (not id or g.user.is_web_url(id)):
|
||||||
actor['id'] = g.user.ap_actor()
|
actor['id'] = g.user.ap_actor()
|
||||||
|
|
||||||
actor.update({
|
actor['url'] = urls[0] if len(urls) == 1 else urls
|
||||||
'url': urls if len(urls) > 1 else urls[0],
|
# required by ActivityPub
|
||||||
# required by ActivityPub
|
# https://www.w3.org/TR/activitypub/#actor-objects
|
||||||
# https://www.w3.org/TR/activitypub/#actor-objects
|
actor.setdefault('inbox', g.user.ap_actor('inbox'))
|
||||||
'inbox': g.user.ap_actor('inbox'),
|
actor.setdefault('outbox', g.user.ap_actor('outbox'))
|
||||||
'outbox': g.user.ap_actor('outbox'),
|
|
||||||
})
|
|
||||||
|
|
||||||
# TODO: genericize (see line 752 in actor())
|
# TODO: genericize (see line 752 in actor())
|
||||||
if g.user.LABEL != 'atproto':
|
if g.user.LABEL != 'atproto':
|
||||||
# This has to be the domain for Mastodon interop/Webfinger discovery!
|
# This has to be the domain for Mastodon interop/Webfinger discovery!
|
||||||
# See related comment in actor() below.
|
# See related comment in actor() below.
|
||||||
actor['preferredUsername'] = domain
|
assert urls
|
||||||
|
actor['preferredUsername'] = util.domain_from_link(
|
||||||
|
unwrap(urls[0]), minimize=False)
|
||||||
|
|
||||||
# Override the label for their home page to be "Web site"
|
# Override the label for their home page to be "Web site"
|
||||||
for att in util.get_list(actor, 'attachment'):
|
for att in util.get_list(actor, 'attachment'):
|
||||||
|
@ -780,7 +789,7 @@ def actor(handle_or_id):
|
||||||
if not g.user.obj or not g.user.obj.as1:
|
if not g.user.obj or not g.user.obj.as1:
|
||||||
g.user.obj = cls.load(g.user.profile_id(), gateway=True)
|
g.user.obj = cls.load(g.user.profile_id(), gateway=True)
|
||||||
|
|
||||||
actor = g.user.as2() or {
|
actor = ActivityPub.convert(g.user.obj) or {
|
||||||
'@context': [as2.CONTEXT],
|
'@context': [as2.CONTEXT],
|
||||||
'type': 'Person',
|
'type': 'Person',
|
||||||
}
|
}
|
||||||
|
@ -893,7 +902,7 @@ def follower_collection(id, collection):
|
||||||
page = {
|
page = {
|
||||||
'type': 'CollectionPage',
|
'type': 'CollectionPage',
|
||||||
'partOf': request.base_url,
|
'partOf': request.base_url,
|
||||||
'items': util.trim_nulls([f.user.as2() for f in followers]),
|
'items': util.trim_nulls([ActivityPub.convert(f.user.obj) for f in followers]),
|
||||||
}
|
}
|
||||||
if new_before:
|
if new_before:
|
||||||
page['next'] = f'{request.base_url}?before={new_before}'
|
page['next'] = f'{request.base_url}?before={new_before}'
|
||||||
|
|
|
@ -110,7 +110,7 @@ class FollowCallback(indieauth.Callback):
|
||||||
return redirect(g.user.user_page_path('following'))
|
return redirect(g.user.user_page_path('following'))
|
||||||
|
|
||||||
followee_id = followee.as1.get('id')
|
followee_id = followee.as1.get('id')
|
||||||
followee_as2 = followee.as_as2()
|
followee_as2 = ActivityPub.convert(followee)
|
||||||
inbox = followee_as2.get('inbox')
|
inbox = followee_as2.get('inbox')
|
||||||
if not followee_id or not inbox:
|
if not followee_id or not inbox:
|
||||||
flash(f"AS2 profile {as2_url} missing id or inbox")
|
flash(f"AS2 profile {as2_url} missing id or inbox")
|
||||||
|
@ -122,7 +122,7 @@ class FollowCallback(indieauth.Callback):
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'type': 'Follow',
|
'type': 'Follow',
|
||||||
'id': follow_id,
|
'id': follow_id,
|
||||||
'object': followee_as2,
|
'object': followee_id,
|
||||||
'actor': g.user.ap_actor(),
|
'actor': g.user.ap_actor(),
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ class UnfollowCallback(indieauth.Callback):
|
||||||
followee.put()
|
followee.put()
|
||||||
|
|
||||||
# TODO(#529): generalize
|
# TODO(#529): generalize
|
||||||
inbox = followee.as2().get('inbox')
|
inbox = ActivityPub.convert(followee.obj).get('inbox')
|
||||||
if not inbox:
|
if not inbox:
|
||||||
flash(f"AS2 profile {followee_id} missing inbox")
|
flash(f"AS2 profile {followee_id} missing inbox")
|
||||||
return redirect(g.user.user_page_path('following'))
|
return redirect(g.user.user_page_path('following'))
|
||||||
|
|
14
models.py
14
models.py
|
@ -306,10 +306,6 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
|
||||||
for u in users:
|
for u in users:
|
||||||
u._obj = keys_to_objs.get(u.obj_key)
|
u._obj = keys_to_objs.get(u.obj_key)
|
||||||
|
|
||||||
def as2(self):
|
|
||||||
"""Returns this user as an AS2 actor."""
|
|
||||||
return self.obj.as_as2() if self.obj else {}
|
|
||||||
|
|
||||||
@ndb.ComputedProperty
|
@ndb.ComputedProperty
|
||||||
def handle(self):
|
def handle(self):
|
||||||
"""This user's unique, human-chosen handle, eg ``@me@snarfed.org``.
|
"""This user's unique, human-chosen handle, eg ``@me@snarfed.org``.
|
||||||
|
@ -540,6 +536,7 @@ class Object(StringIdModel):
|
||||||
# choices is populated in app, after all User subclasses are created,
|
# choices is populated in app, after all User subclasses are created,
|
||||||
# so that PROTOCOLS is fully populated
|
# so that PROTOCOLS is fully populated
|
||||||
# TODO: remove? is this redundant with the protocol-specific data fields below?
|
# TODO: remove? is this redundant with the protocol-specific data fields below?
|
||||||
|
# TODO: otherwise, nail down whether this is ABBREV or LABEL
|
||||||
source_protocol = ndb.StringProperty(choices=[])
|
source_protocol = ndb.StringProperty(choices=[])
|
||||||
labels = ndb.StringProperty(repeated=True, choices=LABELS)
|
labels = ndb.StringProperty(repeated=True, choices=LABELS)
|
||||||
|
|
||||||
|
@ -805,10 +802,6 @@ class Object(StringIdModel):
|
||||||
with self.lock:
|
with self.lock:
|
||||||
setattr(self, prop, None)
|
setattr(self, prop, None)
|
||||||
|
|
||||||
def as_as2(self):
|
|
||||||
"""Returns this object as an AS2 dict."""
|
|
||||||
return self.as2 or as2.from_as1(self.as1) or {}
|
|
||||||
|
|
||||||
def as_bsky(self, fetch_blobs=False):
|
def as_bsky(self, fetch_blobs=False):
|
||||||
"""Returns this object as a Bluesky record.
|
"""Returns this object as a Bluesky record.
|
||||||
|
|
||||||
|
@ -938,7 +931,7 @@ class Object(StringIdModel):
|
||||||
* ``object.inReplyTo``
|
* ``object.inReplyTo``
|
||||||
* ``tags.[objectType=mention].url``
|
* ``tags.[objectType=mention].url``
|
||||||
"""
|
"""
|
||||||
if not self.as1 or not self.source_protocol:
|
if not self.as1:
|
||||||
return
|
return
|
||||||
|
|
||||||
# extract ids, strip Bridgy Fed subdomain URLs
|
# extract ids, strip Bridgy Fed subdomain URLs
|
||||||
|
@ -946,6 +939,9 @@ class Object(StringIdModel):
|
||||||
if outer_obj != self.as1:
|
if outer_obj != self.as1:
|
||||||
self.our_as1 = util.trim_nulls(outer_obj)
|
self.our_as1 = util.trim_nulls(outer_obj)
|
||||||
|
|
||||||
|
if not self.source_protocol:
|
||||||
|
return
|
||||||
|
|
||||||
inner_obj = outer_obj['object'] = as1.get_object(outer_obj)
|
inner_obj = outer_obj['object'] = as1.get_object(outer_obj)
|
||||||
fields = ['actor', 'author', 'inReplyTo']
|
fields = ['actor', 'author', 'inReplyTo']
|
||||||
mention_tags = [t for t in (as1.get_objects(outer_obj, 'tags')
|
mention_tags = [t for t in (as1.get_objects(outer_obj, 'tags')
|
||||||
|
|
2
pages.py
2
pages.py
|
@ -20,6 +20,7 @@ from oauth_dropins.webutil.flask_util import (
|
||||||
redirect,
|
redirect,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from activitypub import ActivityPub
|
||||||
import common
|
import common
|
||||||
from common import DOMAIN_RE
|
from common import DOMAIN_RE
|
||||||
from flask_app import app, cache
|
from flask_app import app, cache
|
||||||
|
@ -165,6 +166,7 @@ def followers_or_following(protocol, id, collection):
|
||||||
f'{collection}.html',
|
f'{collection}.html',
|
||||||
address=request.args.get('address'),
|
address=request.args.get('address'),
|
||||||
follow_url=request.values.get('url'),
|
follow_url=request.values.get('url'),
|
||||||
|
ActivityPub=ActivityPub,
|
||||||
**TEMPLATE_VARS,
|
**TEMPLATE_VARS,
|
||||||
**locals(),
|
**locals(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{{ user_as1.get('displayName') or '' }}
|
{{ user_as1.get('displayName') or '' }}
|
||||||
{{ as2.address(f.user.as2() or url) or url }}
|
{{ as2.address(ActivityPub.convert(f.user.obj) or url) or url }}
|
||||||
</a>
|
</a>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
|
|
@ -1974,10 +1974,31 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
self.assertFalse(ActivityPub.fetch(obj))
|
self.assertFalse(ActivityPub.fetch(obj))
|
||||||
self.assertIsNone(obj.as1)
|
self.assertIsNone(obj.as1)
|
||||||
|
|
||||||
@skip
|
|
||||||
def test_convert(self):
|
def test_convert(self):
|
||||||
obj = Object(id='http://orig', as2=LIKE)
|
obj = Object()
|
||||||
self.assertEqual(LIKE_WRAPPED, ActivityPub.convert(obj))
|
self.assertEqual({}, ActivityPub.convert(obj))
|
||||||
|
|
||||||
|
obj.our_as1 = {}
|
||||||
|
self.assertEqual({}, ActivityPub.convert(obj))
|
||||||
|
|
||||||
|
obj = Object(id='http://orig', our_as1={
|
||||||
|
'id': 'http://user.com/like',
|
||||||
|
'objectType': 'activity',
|
||||||
|
'verb': 'like',
|
||||||
|
'actor': 'https://user.com/',
|
||||||
|
'object': 'https://mas.to/post',
|
||||||
|
})
|
||||||
|
self.assertEqual({
|
||||||
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'id': 'http://localhost/r/http://user.com/like',
|
||||||
|
'type': 'Like',
|
||||||
|
'actor': 'http://localhost/user.com',
|
||||||
|
'object': 'https://mas.to/post',
|
||||||
|
'to': ['https://www.w3.org/ns/activitystreams#Public'],
|
||||||
|
}, ActivityPub.convert(obj))
|
||||||
|
|
||||||
|
obj.as2 = {'baz': 'biff'}
|
||||||
|
self.assertEqual({'baz': 'biff'}, ActivityPub.convert(obj))
|
||||||
|
|
||||||
def test_postprocess_as2_idempotent(self):
|
def test_postprocess_as2_idempotent(self):
|
||||||
g.user = self.make_user('foo.com')
|
g.user = self.make_user('foo.com')
|
||||||
|
|
|
@ -35,13 +35,14 @@ FOLLOWEE = {
|
||||||
'id': 'https://bar/id',
|
'id': 'https://bar/id',
|
||||||
'url': 'https://bar/url',
|
'url': 'https://bar/url',
|
||||||
'inbox': 'http://bar/inbox',
|
'inbox': 'http://bar/inbox',
|
||||||
|
'outbox': 'http://bar/outbox',
|
||||||
}
|
}
|
||||||
FOLLOW_ADDRESS = {
|
FOLLOW_ADDRESS = {
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'type': 'Follow',
|
'type': 'Follow',
|
||||||
'id': f'http://localhost/web/alice.com/following#2022-01-02T03:04:05-@foo@bar',
|
'id': f'http://localhost/web/alice.com/following#2022-01-02T03:04:05-@foo@bar',
|
||||||
'actor': 'http://localhost/alice.com',
|
'actor': 'http://localhost/alice.com',
|
||||||
'object': FOLLOWEE,
|
'object': FOLLOWEE['id'],
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
}
|
}
|
||||||
FOLLOW_URL = copy.deepcopy(FOLLOW_ADDRESS)
|
FOLLOW_URL = copy.deepcopy(FOLLOW_ADDRESS)
|
||||||
|
@ -188,10 +189,7 @@ class FollowTest(TestCase):
|
||||||
self.check('https://bar/actor', resp, FOLLOW_URL, mock_get, mock_post)
|
self.check('https://bar/actor', resp, FOLLOW_URL, mock_get, mock_post)
|
||||||
|
|
||||||
def test_callback_stored_followee_with_our_as1(self, mock_get, mock_post):
|
def test_callback_stored_followee_with_our_as1(self, mock_get, mock_post):
|
||||||
self.store_object(id='https://bar/id', our_as1=as2.to_as1({
|
self.store_object(id='https://bar/id', our_as1=as2.to_as1(FOLLOWEE))
|
||||||
**FOLLOWEE,
|
|
||||||
# 'id': 'https://bar/actor',
|
|
||||||
}))
|
|
||||||
|
|
||||||
mock_get.side_effect = (
|
mock_get.side_effect = (
|
||||||
requests_response(''),
|
requests_response(''),
|
||||||
|
@ -208,14 +206,7 @@ class FollowTest(TestCase):
|
||||||
follow_with_profile_link = {
|
follow_with_profile_link = {
|
||||||
**FOLLOW_URL,
|
**FOLLOW_URL,
|
||||||
'id': f'http://localhost/web/alice.com/following#2022-01-02T03:04:05-https://bar/id',
|
'id': f'http://localhost/web/alice.com/following#2022-01-02T03:04:05-https://bar/id',
|
||||||
'object': {
|
'object': 'https://bar/id',
|
||||||
**FOLLOWEE,
|
|
||||||
'attachment': [{
|
|
||||||
'type': 'PropertyValue',
|
|
||||||
'name': 'Link',
|
|
||||||
'value': '<a rel="me" href="https://bar/url"><span class="invisible">https://</span>bar/url</a>',
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
self.check('https://bar/id', resp, follow_with_profile_link, mock_get,
|
self.check('https://bar/id', resp, follow_with_profile_link, mock_get,
|
||||||
mock_post, fetched_followee=False)
|
mock_post, fetched_followee=False)
|
||||||
|
@ -246,9 +237,7 @@ class FollowTest(TestCase):
|
||||||
state = util.encode_oauth_state(self.state)
|
state = util.encode_oauth_state(self.state)
|
||||||
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
|
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
|
||||||
|
|
||||||
expected_follow = copy.deepcopy(FOLLOW_URL)
|
self.check('https://bar/actor', resp, FOLLOW_URL, mock_get, mock_post)
|
||||||
expected_follow['object'] = followee
|
|
||||||
self.check('https://bar/actor', resp, expected_follow, mock_get, mock_post)
|
|
||||||
|
|
||||||
def check(self, input, resp, expected_follow, mock_get, mock_post,
|
def check(self, input, resp, expected_follow, mock_get, mock_post,
|
||||||
fetched_followee=True):
|
fetched_followee=True):
|
||||||
|
@ -321,18 +310,14 @@ class FollowTest(TestCase):
|
||||||
|
|
||||||
id = 'http://localhost/web/www.alice.com/following#2022-01-02T03:04:05-https://bar/actor'
|
id = 'http://localhost/web/www.alice.com/following#2022-01-02T03:04:05-https://bar/actor'
|
||||||
expected_follow = {
|
expected_follow = {
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
**FOLLOW_URL,
|
||||||
'type': 'Follow',
|
|
||||||
'id': id,
|
'id': id,
|
||||||
'actor': 'http://localhost/www.alice.com',
|
'actor': 'http://localhost/www.alice.com',
|
||||||
'object': FOLLOWEE,
|
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
|
||||||
}
|
}
|
||||||
followee = ActivityPub(id='https://bar/id').key
|
followee = ActivityPub(id='https://bar/id').key
|
||||||
follow_obj = self.assert_object(
|
follow_obj = self.assert_object(
|
||||||
id, users=[user.key, followee], status='complete',
|
id, users=[user.key, followee], status='complete',
|
||||||
labels=['user', 'activity'], source_protocol='ui', as2=expected_follow,
|
labels=['user', 'activity'], source_protocol='ui', as2=expected_follow)
|
||||||
as1=as2.to_as1(expected_follow))
|
|
||||||
|
|
||||||
followers = Follower.query().fetch()
|
followers = Follower.query().fetch()
|
||||||
self.assert_entities_equal(
|
self.assert_entities_equal(
|
||||||
|
@ -363,11 +348,7 @@ class FollowTest(TestCase):
|
||||||
state = util.encode_oauth_state(self.state)
|
state = util.encode_oauth_state(self.state)
|
||||||
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
|
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
|
||||||
|
|
||||||
expected_follow = {
|
self.check('https://bar/actor', resp, FOLLOW_URL, mock_get, mock_post)
|
||||||
**FOLLOW_URL,
|
|
||||||
'object': followee,
|
|
||||||
}
|
|
||||||
self.check('https://bar/actor', resp, expected_follow, mock_get, mock_post)
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[f'Followed <a href="https://bar/url">https://bar/actor</a>.'],
|
[f'Followed <a href="https://bar/url">https://bar/actor</a>.'],
|
||||||
get_flashed_messages())
|
get_flashed_messages())
|
||||||
|
|
|
@ -160,18 +160,6 @@ class UserTest(TestCase):
|
||||||
def test_handle(self):
|
def test_handle(self):
|
||||||
self.assertEqual('y.z', g.user.handle)
|
self.assertEqual('y.z', g.user.handle)
|
||||||
|
|
||||||
def test_as2(self):
|
|
||||||
self.assertEqual({}, g.user.as2())
|
|
||||||
|
|
||||||
obj = Object(id='foo')
|
|
||||||
g.user.obj_key = obj.key # doesn't exist
|
|
||||||
self.assertEqual({}, g.user.as2())
|
|
||||||
|
|
||||||
del g.user._obj
|
|
||||||
obj.as2 = {'foo': 'bar'}
|
|
||||||
obj.put()
|
|
||||||
self.assertEqual({'foo': 'bar'}, g.user.as2())
|
|
||||||
|
|
||||||
def test_id_as(self):
|
def test_id_as(self):
|
||||||
user = self.make_user('fake:user', cls=Fake)
|
user = self.make_user('fake:user', cls=Fake)
|
||||||
self.assertEqual('fake:user', user.id_as(Fake))
|
self.assertEqual('fake:user', user.id_as(Fake))
|
||||||
|
@ -532,26 +520,6 @@ class ObjectTest(TestCase):
|
||||||
obj.put()
|
obj.put()
|
||||||
self.assertEqual(['user'], obj.labels)
|
self.assertEqual(['user'], obj.labels)
|
||||||
|
|
||||||
def test_as_as2(self):
|
|
||||||
obj = Object()
|
|
||||||
self.assertEqual({}, obj.as_as2())
|
|
||||||
|
|
||||||
obj.our_as1 = {}
|
|
||||||
self.assertEqual({}, obj.as_as2())
|
|
||||||
|
|
||||||
obj.our_as1 = {
|
|
||||||
'objectType': 'person',
|
|
||||||
'foo': 'bar',
|
|
||||||
}
|
|
||||||
self.assertEqual({
|
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
||||||
'type': 'Person',
|
|
||||||
'foo': 'bar',
|
|
||||||
}, obj.as_as2())
|
|
||||||
|
|
||||||
obj.as2 = {'baz': 'biff'}
|
|
||||||
self.assertEqual({'baz': 'biff'}, obj.as_as2())
|
|
||||||
|
|
||||||
def test_as1_from_as2(self):
|
def test_as1_from_as2(self):
|
||||||
self.assert_equals({
|
self.assert_equals({
|
||||||
'objectType': 'person',
|
'objectType': 'person',
|
||||||
|
|
|
@ -1432,7 +1432,7 @@ class ProtocolReceiveTest(TestCase):
|
||||||
Fake.receive(obj)
|
Fake.receive(obj)
|
||||||
self.assert_equals({
|
self.assert_equals({
|
||||||
**follow,
|
**follow,
|
||||||
'actor': 'fake:alice',
|
'actor': {'id': 'fake:alice'},
|
||||||
'object': 'other:bob',
|
'object': 'other:bob',
|
||||||
}, Object.get_by_id('fake:follow').our_as1)
|
}, Object.get_by_id('fake:follow').our_as1)
|
||||||
|
|
||||||
|
|
|
@ -62,17 +62,6 @@ ACTOR_AS2 = {
|
||||||
'inbox': 'http://localhost/user.com/inbox',
|
'inbox': 'http://localhost/user.com/inbox',
|
||||||
'outbox': 'http://localhost/user.com/outbox',
|
'outbox': 'http://localhost/user.com/outbox',
|
||||||
}
|
}
|
||||||
ACTOR_AS2_USER = {
|
|
||||||
'type': 'Person',
|
|
||||||
'id': 'https://user.com/',
|
|
||||||
'url': 'https://user.com/',
|
|
||||||
'name': 'Ms. ☕ Baz',
|
|
||||||
'attachment': [{
|
|
||||||
'name': 'Ms. ☕ Baz',
|
|
||||||
'type': 'PropertyValue',
|
|
||||||
'value': '<a rel="me" href="https://user.com"><span class="invisible">https://</span>user.com</a>',
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
ACTOR_AS2_FULL = {
|
ACTOR_AS2_FULL = {
|
||||||
**ACTOR_AS2,
|
**ACTOR_AS2,
|
||||||
'@context': [
|
'@context': [
|
||||||
|
@ -931,8 +920,8 @@ class WebTest(TestCase):
|
||||||
self.assertEqual(('https://mas.to/inbox',), args)
|
self.assertEqual(('https://mas.to/inbox',), args)
|
||||||
self.assert_equals(AS2_CREATE, json_loads(kwargs['data']))
|
self.assert_equals(AS2_CREATE, json_loads(kwargs['data']))
|
||||||
|
|
||||||
def test_like_stored_object_without_as2(self, mock_get, mock_post):
|
def test_like_stored_object(self, mock_get, mock_post):
|
||||||
Object(id='https://mas.to/toot', mf2=NOTE_MF2, source_protocol='ap').put()
|
Object(id='https://mas.to/toot', source_protocol='ap').put()
|
||||||
Object(id='https://user.com/', mf2=ACTOR_MF2).put()
|
Object(id='https://user.com/', mf2=ACTOR_MF2).put()
|
||||||
mock_get.side_effect = [
|
mock_get.side_effect = [
|
||||||
LIKE,
|
LIKE,
|
||||||
|
@ -1608,15 +1597,20 @@ class WebTest(TestCase):
|
||||||
expected_as2)
|
expected_as2)
|
||||||
|
|
||||||
# updated Web user
|
# updated Web user
|
||||||
self.assert_user(Web, 'user.com',
|
expected_actor_as2 = {
|
||||||
obj_as2={
|
'type': 'Person',
|
||||||
**ACTOR_AS2_USER,
|
'id': 'https://user.com/',
|
||||||
'updated': '2022-01-02T03:04:05+00:00',
|
'url': 'https://user.com/',
|
||||||
},
|
'name': 'Ms. ☕ Baz',
|
||||||
direct=True,
|
'attachment': [{
|
||||||
has_redirects=True,
|
'name': 'Ms. ☕ Baz',
|
||||||
)
|
'type': 'PropertyValue',
|
||||||
|
'value': '<a rel="me" href="https://user.com"><span class="invisible">https://</span>user.com</a>',
|
||||||
|
}],
|
||||||
|
'updated': '2022-01-02T03:04:05+00:00',
|
||||||
|
}
|
||||||
|
self.assert_user(Web, 'user.com', obj_as2=expected_actor_as2, direct=True,
|
||||||
|
has_redirects=True)
|
||||||
|
|
||||||
# homepage object
|
# homepage object
|
||||||
actor = {
|
actor = {
|
||||||
|
@ -1841,7 +1835,7 @@ http://this/404s
|
||||||
# preferredUsername stays y.z despite user's username. since Mastodon
|
# preferredUsername stays y.z despite user's username. since Mastodon
|
||||||
# queries Webfinger for preferredUsername@fed.brid.gy
|
# queries Webfinger for preferredUsername@fed.brid.gy
|
||||||
# https://github.com/snarfed/bridgy-fed/issues/77#issuecomment-949955109
|
# https://github.com/snarfed/bridgy-fed/issues/77#issuecomment-949955109
|
||||||
postprocessed = postprocess_as2(g.user.as2())
|
postprocessed = ActivityPub.convert(g.user.obj)
|
||||||
self.assertEqual('user.com', postprocessed['preferredUsername'])
|
self.assertEqual('user.com', postprocessed['preferredUsername'])
|
||||||
|
|
||||||
def test_web_url(self, _, __):
|
def test_web_url(self, _, __):
|
||||||
|
|
|
@ -456,7 +456,7 @@ class TestCase(unittest.TestCase, testutil.Asserts):
|
||||||
|
|
||||||
obj_as2 = props.pop('obj_as2', None)
|
obj_as2 = props.pop('obj_as2', None)
|
||||||
if obj_as2:
|
if obj_as2:
|
||||||
self.assert_equals(obj_as2, got.as2())
|
self.assert_equals(obj_as2, as2.from_as1(got.obj.as1))
|
||||||
|
|
||||||
# generated, computed, etc
|
# generated, computed, etc
|
||||||
ignore = ['created', 'mod', 'handle', 'obj_key', 'private_exponent',
|
ignore = ['created', 'mod', 'handle', 'obj_key', 'private_exponent',
|
||||||
|
|
Ładowanie…
Reference in New Issue