Object._pre_put_hook: require that protocol owns id

pull/785/head
Ryan Barrett 2024-01-12 19:52:49 -08:00
rodzic e39f67a0e7
commit 7941b632ca
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
8 zmienionych plików z 92 dodań i 82 usunięć

Wyświetl plik

@ -658,10 +658,16 @@ class Object(StringIdModel):
* Set/remove the activity label
* Strip @context from as2 (we don't do LD) to save disk space
"""
assert '^^' not in self.key.id()
id = self.key.id()
assert '^^' not in id
if self.key.id().startswith('at://'):
repo, _, _ = parse_at_uri(self.key.id())
if self.source_protocol not in (None, 'ui'):
proto = PROTOCOLS[self.source_protocol]
assert proto.owns_id(id) is not False, \
f'Protocol {proto.LABEL} does not own id {id}'
if id.startswith('at://'):
repo, _, _ = parse_at_uri(id)
if not repo.startswith('did:'):
# TODO: if we hit this, that means the AppView gave us an AT URI
# with a handle repo/authority instead of DID. that's surprising!
@ -669,7 +675,7 @@ class Object(StringIdModel):
# arroba.did.canonicalize_at_uri() function, then use it here,
# or before.
raise ValueError(
f'at:// URI ids must have DID repos; got {self.key.id()}')
f'at:// URI ids must have DID repos; got {id}')
if self.as1 and self.as1.get('objectType') == 'activity':
# can't self.add because we're inside self.put, which has the lock

Wyświetl plik

@ -629,14 +629,14 @@ class ActivityPubTest(TestCase):
})
reply = {
**REPLY_OBJECT,
'id': 'fake:my-reply',
'id': 'http://my/reply',
'inReplyTo': 'fake:post',
}
got = self.post('/ap/fake:user/inbox', json=reply,
base_url='https://fa.brid.gy/')
self.assertEqual(202, got.status_code)
self.assertEqual([('fake:my-reply#bridgy-fed-create', 'fake:post:target')],
self.assertEqual([('http://my/reply#bridgy-fed-create', 'fake:post:target')],
Fake.sent)
def test_inbox_reply_to_self_domain(self, mock_head, mock_get, mock_post):
@ -670,7 +670,7 @@ class ActivityPubTest(TestCase):
swentel = self.make_user('https://mas.to/users/swentel', cls=ActivityPub)
Follower.get_or_create(to=swentel, from_=self.user)
bar = self.make_user('fake:bar', cls=Fake, obj_id='fake:bar')
Follower.get_or_create(to=self.make_user('https://other.actor',
Follower.get_or_create(to=self.make_user('https://other/actor',
cls=ActivityPub),
from_=bar)
baz = self.make_user('fake:baz', cls=Fake, obj_id='fake:baz')
@ -784,7 +784,7 @@ class ActivityPubTest(TestCase):
self.assert_object(REPOST['id'],
source_protocol='activitypub',
status='complete',
our_as1=as2.to_as1({**REPOST, 'actor': ACTOR}),
as2=REPOST,
users=[self.swentel_key],
feed=[self.user.key, baz.key],
delivered=['shared:target'],
@ -1111,13 +1111,11 @@ class ActivityPubTest(TestCase):
def test_inbox_follow_inactive(self, mock_head, mock_get, mock_post):
follower = Follower.get_or_create(
to=self.user,
from_=self.make_user(ACTOR['id'], cls=ActivityPub),
from_=self.make_user(ACTOR['id'], cls=ActivityPub, obj_as2=ACTOR),
status='inactive')
mock_head.return_value = requests_response(url='https://user.com/')
mock_get.side_effect = [
# source actor
self.as2_resp(FOLLOW_WITH_ACTOR['actor']),
test_web.ACTOR_HTML_RESP,
WEBMENTION_DISCOVERY,
]
@ -1468,12 +1466,12 @@ class ActivityPubTest(TestCase):
with self.assertLogs() as logs:
got = self.post('/user.com/inbox', json={
**NOTE_OBJECT,
'author': 'https://alice',
'author': 'https://al/ice',
})
self.assertEqual(204, got.status_code, got.get_data(as_text=True))
self.assertIn(
"WARNING:protocol:actor https://alice isn't authed user http://my/key/id",
"WARNING:protocol:actor https://al/ice isn't authed user http://my/key/id",
logs.output)
def test_followers_collection_unknown_user(self, *_):
@ -1504,7 +1502,7 @@ class ActivityPubTest(TestCase):
from_=self.make_user('http://bar', cls=ActivityPub, obj_as2=ACTOR),
follow=follow)
Follower.get_or_create(
to=self.make_user('https://other.actor', cls=ActivityPub),
to=self.make_user('https://other/actor', cls=ActivityPub),
from_=self.user)
Follower.get_or_create(
to=self.user,
@ -1512,24 +1510,24 @@ class ActivityPubTest(TestCase):
follow=follow)
Follower.get_or_create(
to=self.user,
from_=self.make_user('http://baj', cls=Fake),
from_=self.make_user('fake:baj', cls=Fake),
status='inactive')
def test_followers_collection_fake(self, *_):
self.make_user('foo.com', cls=Fake)
self.make_user('fake:foo', cls=Fake)
resp = self.client.get('/ap/foo.com/followers',
resp = self.client.get('/ap/fake:foo/followers',
base_url='https://fa.brid.gy')
self.assertEqual(200, resp.status_code)
self.assertEqual({
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'https://fa.brid.gy/ap/foo.com/followers',
'id': 'https://fa.brid.gy/ap/fake:foo/followers',
'type': 'Collection',
'summary': "foo.com's followers",
'summary': "fake:foo's followers",
'totalItems': 0,
'first': {
'type': 'CollectionPage',
'partOf': 'https://fa.brid.gy/ap/foo.com/followers',
'partOf': 'https://fa.brid.gy/ap/fake:foo/followers',
'items': [],
},
}, resp.json)
@ -1601,12 +1599,12 @@ class ActivityPubTest(TestCase):
follow=follow)
Follower.get_or_create(
to=self.user,
from_=self.make_user('https://other.actor', cls=ActivityPub))
from_=self.make_user('https://other/actor', cls=ActivityPub))
Follower.get_or_create(
to=self.make_user('http://baz', cls=ActivityPub, obj_as2=ACTOR),
from_=self.user, follow=follow)
Follower.get_or_create(
to=self.make_user('http://baj', cls=ActivityPub),
to=self.make_user('http://ba/j', cls=ActivityPub),
from_=self.user,
status='inactive')
@ -2212,9 +2210,9 @@ class ActivityPubUtilsTest(TestCase):
self.assertEqual('http://my/url', user.web_url())
def test_handle(self):
user = self.make_user('http://foo', cls=ActivityPub)
user = self.make_user('http://foo/ey', cls=ActivityPub)
self.assertIsNone(user.handle)
self.assertEqual('http://foo', user.handle_or_id())
self.assertEqual('http://foo/ey', user.handle_or_id())
user.obj = Object(id='a', as2=ACTOR)
self.assertEqual('@swentel@mas.to', user.handle)

Wyświetl plik

@ -157,12 +157,12 @@ class ConvertTest(testutil.TestCase):
mock_get.assert_has_calls((self.as2_req('http://foo'),))
def test_activitypub_to_web_with_author(self):
Object(id='http://foo', our_as1={**COMMENT, 'author': 'http://bar'},
Object(id='http://fo/o', our_as1={**COMMENT, 'author': 'http://ba/r'},
source_protocol='activitypub').put()
Object(id='http://bar', our_as1=ACTOR,
Object(id='http://ba/r', our_as1=ACTOR,
source_protocol='activitypub').put()
resp = self.client.get('/convert/web/http://foo',
resp = self.client.get('/convert/web/http://fo/o',
base_url='https://ap.brid.gy/')
self.assertEqual(200, resp.status_code)
self.assert_multiline_equals(AUTHOR_HTML, resp.get_data(as_text=True),

Wyświetl plik

@ -129,7 +129,9 @@ class UserTest(TestCase):
def test_user_page_path(self):
self.assertEqual('/web/y.z', self.user.user_page_path())
self.assertEqual('/web/y.z/followers', self.user.user_page_path('followers'))
self.assertEqual('/fa/foo', self.make_user('foo', cls=Fake).user_page_path())
fake_foo = self.make_user('fake:foo', cls=Fake)
self.assertEqual('/fa/fake:handle:foo', fake_foo.user_page_path())
def test_user_link(self):
self.assert_multiline_equals("""\
@ -486,8 +488,9 @@ class ObjectTest(TestCase):
self.assertIn('Alice', got)
def test_actor_link_object_in_datastore(self):
Object(id='fake:alice', as2={"name": "Alice"}).put()
obj = Object(id='x', source_protocol='fake', our_as1={'actor': 'fake:alice'})
Object(id='fake:alice', as2={'name': 'Alice'}).put()
obj = Object(id='fake:bob', source_protocol='fake',
our_as1={'actor': 'fake:alice'})
self.assertIn('Alice', obj.actor_link())
def test_actor_link_no_image(self):
@ -674,6 +677,13 @@ class ObjectTest(TestCase):
'object': {},
}, obj.key.get().as2)
def test_put_requires_protocol_owns_id(self):
Object(id='asdf foo').put() # ok, no source protocol
Object(id='fake:foo', source_protocol='fake').put() # ok, valid id
with self.assertRaises(AssertionError):
Object(id='not a fake', source_protocol='fake').put()
def test_resolve_ids_empty(self):
obj = Object()
obj.resolve_ids()
@ -839,8 +849,8 @@ class FollowerTest(TestCase):
def setUp(self):
super().setUp()
self.user = self.make_user('foo', cls=Fake)
self.other_user = self.make_user('bar', cls=Fake)
self.user = self.make_user('fake:foo', cls=Fake)
self.other_user = self.make_user('fake:bar', cls=Fake)
def test_from_to_same_type_fails(self):
with self.assertRaises(AssertionError):
@ -861,7 +871,7 @@ class FollowerTest(TestCase):
self.assertEqual(1, Follower.query().count())
Follower.get_or_create(to=self.user, from_=self.other_user)
Follower.get_or_create(from_=self.user, to=self.make_user('baz', cls=Fake))
Follower.get_or_create(from_=self.user, to=self.make_user('fake:baz', cls=Fake))
self.assertEqual(3, Follower.query().count())
# check that kwargs get set on existing entity

Wyświetl plik

@ -23,8 +23,8 @@ from granary.tests.test_bluesky import ACTOR_AS, ACTOR_PROFILE_BSKY
from .test_web import ACTOR_AS2, REPOST_AS2
ACTOR_WITH_PREFERRED_USERNAME = {
**ACTOR,
'preferredUsername': 'me',
'url': 'https://plus.google.com/bob',
}
@ -56,7 +56,7 @@ class PagesTest(TestCase):
self.assert_equals(200, got.status_code)
def test_user_page_handle(self):
user = self.make_user('http://foo', cls=ActivityPub,
user = self.make_user('http://fo/o', cls=ActivityPub,
obj_as2=ACTOR_WITH_PREFERRED_USERNAME)
self.assertEqual('@me@plus.google.com', user.handle_as(ActivityPub))
@ -167,9 +167,9 @@ class PagesTest(TestCase):
def test_followers(self):
Follower.get_or_create(
to=self.user,
from_=self.make_user('http://unused', cls=ActivityPub, obj_as2={
from_=self.make_user('http://un/used', cls=ActivityPub, obj_as2={
**ACTOR,
'id': 'unused',
'id': 'http://un/used',
'url': 'http://stored/users/follow',
}))
Follower.get_or_create(
@ -261,9 +261,9 @@ class PagesTest(TestCase):
def test_following(self):
Follower.get_or_create(
from_=self.user,
to=self.make_user('http://unused', cls=ActivityPub, obj_as2={
to=self.make_user('http://un/used', cls=ActivityPub, obj_as2={
**ACTOR,
'id': 'unused',
'id': 'http://un/used',
'url': 'http://stored/users/follow',
}))
Follower.get_or_create(

Wyświetl plik

@ -230,16 +230,16 @@ class ProtocolTest(TestCase):
self.assertEqual(['foo'], Fake.fetched)
def test_load_remote_true_unchanged(self):
obj = self.store_object(id='foo', our_as1={'x': 'stored'},
obj = self.store_object(id='fake:foo', our_as1={'x': 'stored'},
source_protocol='fake')
Fake.fetchable['foo'] = {'x': 'stored'}
Fake.fetchable['fake:foo'] = {'x': 'stored'}
loaded = Fake.load('foo', remote=True)
loaded = Fake.load('fake:foo', remote=True)
self.assert_entities_equal(obj, loaded,
ignore=['expire', 'created', 'updated'])
self.assertFalse(loaded.changed)
self.assertFalse(loaded.new)
self.assertEqual(['foo'], Fake.fetched)
self.assertEqual(['fake:foo'], Fake.fetched)
def test_load_remote_true_local_false(self):
Fake.fetchable['foo'] = our_as1={'x': 'y'}
@ -639,11 +639,11 @@ class ProtocolReceiveTest(TestCase):
self.assertEqual([(obj.key.id(), 'shared:target')], Fake.sent)
def test_create_post_use_instead(self):
self.make_user('fake:instead', cls=Fake, use_instead=self.user.key, obj_mf2={
self.make_user('fake:not-this', cls=Fake, use_instead=self.user.key, obj_mf2={
'type': ['h-card'],
'properties': {
# this is the key part to test; Object.as1 uses this as id
'url': ['https://www.user.com/'],
'url': ['fake:user'],
},
})
self.make_followers()
@ -651,7 +651,7 @@ class ProtocolReceiveTest(TestCase):
post_as1 = {
'id': 'fake:post',
'objectType': 'note',
'author': 'fake:instead',
'author': 'fake:user',
}
obj = self.store_object(id='fake:post', our_as1=post_as1)
@ -888,7 +888,7 @@ class ProtocolReceiveTest(TestCase):
'author': 'fake:bob',
},
}
self.assertEqual(('OK', 202), Fake.receive_as1(reply_as1))
self.assertEqual(('OK', 202), OtherFake.receive_as1(reply_as1))
obj = Object.get_by_id('other:reply#bridgy-fed-create')
self.assertEqual([Fake(id='fake:bob').key], obj.notify)
@ -1515,7 +1515,7 @@ class ProtocolReceiveTest(TestCase):
}
follow_as1 = {
'id': 'http://x.com/follow',
'id': 'other:follow',
'objectType': 'activity',
'verb': 'follow',
'actor': 'other:carol',
@ -1526,18 +1526,20 @@ class ProtocolReceiveTest(TestCase):
self.assertEqual(1, len(OtherFake.sent))
self.assertEqual(
'https://fa.brid.gy/ap/fake:alice/followers#accept-http://x.com/follow',
'https://fa.brid.gy/ap/fake:alice/followers#accept-other:follow',
OtherFake.sent[0][0])
self.assertEqual(1, len(Fake.sent))
self.assertEqual('http://x.com/follow', Fake.sent[0][0])
self.assertEqual('other:follow', Fake.sent[0][0])
followers = Follower.query().fetch()
self.assertEqual(1, len(followers))
self.assertEqual(self.alice.key, followers[0].to)
def test_skip_same_domain(self):
Fake.fetchable = {
@patch('requests.post')
@patch('requests.get')
def test_skip_web_same_domain(self, mock_get, mock_post):
Web.fetchable = {
'http://x.com/alice': {},
'http://x.com/bob': {},
'http://x.com/eve': {},
@ -1552,23 +1554,11 @@ class ProtocolReceiveTest(TestCase):
}
with self.assertRaises(NoContent):
Fake.receive_as1(follow_as1)
Web.receive(Object(our_as1=follow_as1))
self.assertEqual([
('https://fa.brid.gy/ap/http://x.com/bob/followers#accept-http://x.com/follow',
'http://x.com/alice:target'),
('https://fa.brid.gy/ap/http://x.com/eve/followers#accept-http://x.com/follow',
'http://x.com/alice:target'),
], Fake.sent)
self.assert_object('http://x.com/follow',
our_as1=follow_as1,
status='ignored',
users=[ndb.Key(Fake, 'http://x.com/alice')],
notify=[ndb.Key(Fake, 'http://x.com/bob'),
ndb.Key(Fake, 'http://x.com/eve')],
)
self.assertEqual(2, Follower.query().count())
mock_get.assert_not_called()
mock_post.assert_not_called()
self.assertEqual(0, Follower.query().count())
def test_opted_out(self):
self.user.obj.our_as1 = {
@ -1672,7 +1662,7 @@ class ProtocolReceiveTest(TestCase):
# no matching copies
obj = Object(id='other:reply', our_as1=reply, source_protocol='other')
with self.assertRaises(NoContent):
Fake.receive(obj)
OtherFake.receive(obj)
self.assert_equals(reply, obj.our_as1)
# matching copies
@ -1688,7 +1678,7 @@ class ProtocolReceiveTest(TestCase):
protocol.seen_ids.clear()
obj.new = True
self.assertEqual(('OK', 202), Fake.receive(obj))
self.assertEqual(('OK', 202), OtherFake.receive(obj))
self.assertEqual({
'id': 'other:reply',
'objectType': 'note',

Wyświetl plik

@ -1278,7 +1278,7 @@ class WebTest(TestCase):
Follower.get_or_create(
to=self.user,
from_=self.make_user('http://a', cls=ActivityPub,
from_=self.make_user('https://a/b', cls=ActivityPub,
obj_as2={'inbox': 'https://inbox'}))
got = self.post('/queue/webmention', data={
'source': 'https://user.com/post',
@ -1610,13 +1610,13 @@ class WebTest(TestCase):
mock_get.side_effect = [ACTOR_HTML_RESP]
mock_post.return_value = requests_response('abc xyz')
Follower.get_or_create(to=self.user, from_=self.make_user(
'http://ccc', cls=ActivityPub, obj_as2={
'http://c/cc', cls=ActivityPub, obj_as2={
'endpoints': {
'sharedInbox': 'https://shared/inbox',
},
}))
Follower.get_or_create(to=self.user, from_=self.make_user(
'http://ddd', cls=ActivityPub, obj_as2={'inbox': 'https://inbox'}))
'http://d/dd', cls=ActivityPub, obj_as2={'inbox': 'https://inbox'}))
got = self.post('/queue/webmention', data={
'source': 'https://user.com/',
@ -1705,7 +1705,7 @@ class WebTest(TestCase):
mock_post.return_value = requests_response('OK')
Follower.get_or_create(to=self.user, from_=self.make_user(
'http://ddd', cls=ActivityPub, obj_as2={'inbox': 'https://inbox'}))
'http://d/dd', cls=ActivityPub, obj_as2={'inbox': 'https://inbox'}))
got = self.post('/queue/webmention', data={
'source': 'https://user.com/',
@ -2771,7 +2771,7 @@ class WebUtilTest(TestCase):
}), from_user=None), ignore_blanks=True)
def test_target_for(self, _, __):
self.assertIsNone(Web.target_for(Object(id='x', source_protocol='web')))
self.assertIsNone(Web.target_for(Object(id='x')))
self.assertEqual('http://foo', Web.target_for(
Object(id='http://foo', source_protocol='web')))

Wyświetl plik

@ -284,13 +284,19 @@ class TestCase(unittest.TestCase, testutil.Asserts):
obj_as2 = kwargs.pop('obj_as2', None)
obj_mf2 = kwargs.pop('obj_mf2', None)
obj_id = kwargs.pop('obj_id', None)
if not obj_id:
obj_id = ((obj_as2 or {}).get('id')
or util.get_url((obj_mf2 or {}), 'properties')
or str(self.last_make_user_id))
self.last_make_user_id += 1
obj_key = Object(id=obj_id, our_as1=obj_as1, as2=obj_as2, mf2=obj_mf2,
source_protocol=cls.LABEL).put()
obj_key = None
if cls != ATProto:
if not obj_id:
obj_id = ((obj_as2 or {}).get('id')
or util.get_url((obj_mf2 or {}), 'properties')
or (f'http://{id}/' if cls == Web else id))
# unused right now
# or f'fake:{str(self.last_make_user_id)}')
self.last_make_user_id += 1
obj_key = Object.get_or_create(id=obj_id, our_as1=obj_as1, as2=obj_as2,
mf2=obj_mf2, source_protocol=cls.LABEL
).key
user = cls(id=id,
direct=True,