extract new check_can_migrate_out method out of ActivityPub.migrate_out

for snarfed/bounce#40, snarfed/bounce#17
pull/1999/head
Ryan Barrett 2025-07-21 14:24:51 -07:00
rodzic 343cc0690a
commit 3be771a5b8
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
4 zmienionych plików z 100 dodań i 16 usunięć

Wyświetl plik

@ -540,25 +540,10 @@ class ActivityPub(User, Protocol):
Raises:
ValueError: eg if ``ActivityPub`` doesn't own ``to_user_id``
"""
def _error(msg):
logger.warning(msg)
raise ValueError(msg)
user_ap_id = user.id_as(cls)
logger.info(f"Migrating {user.key.id()} 's bridged AP actor {user_ap_id} to {to_user_id}")
if cls.owns_id(to_user_id) is False:
_error(f"{to_user_id} doesn't look like an {cls.LABEL} id")
elif isinstance(user, cls):
_error(f"{user.handle_or_id()} is on {cls.PHRASE}")
elif not user.is_enabled(cls):
_error(f"{user.handle_or_id()} isn't currently bridged to {cls.PHRASE}")
# check that the destination actor has an alias to the bridged actor
to_actor = cls.load(to_user_id, remote=True)
aka = util.get_list(to_actor.as2, 'alsoKnownAs')
if user_ap_id not in aka:
_error(f"{to_user_id} 's alsoKnownAs {aka} doesn't contain {user_ap_id}")
cls.check_can_migrate_out(user, to_user_id)
# send a Move activity to all followers' inboxes
id = f'{user_ap_id}#move-{to_user_id}'
@ -582,6 +567,33 @@ class ActivityPub(User, Protocol):
return ret
@classmethod
def check_can_migrate_out(cls, user, to_user_id):
"""Raises an exception if a user can't yet migrate to a native AP account.
For example, if ``to_user_id`` isn't an ActivityPub actor id, or if it
doesn't have ``user``'s bridged AP id in its ``alsoKnownAs``.
Args:
user (models.User)
to_user_id (str)
Raises:
ValueError: if ``user`` can't migrate to ActivityPub or ``to_user_id`` yet
"""
super().check_can_migrate_out(user, to_user_id)
# check that the destination actor has an alias to the bridged actor
if not (to_actor := cls.load(to_user_id, remote=True)):
raise ValueError("Couldn't fetch {to_user_id}")
aka = util.get_list(to_actor.as2, 'alsoKnownAs')
user_ap_id = user.id_as(cls)
if user_ap_id not in aka:
msg = f"{to_user_id} 's alsoKnownAs doesn't contain {user_ap_id}: {aka}"
logger.warning(msg)
raise ValueError(msg)
@classmethod
def verify_signature(cls, activity):
"""Verifies the current request's HTTP Signature.

Wyświetl plik

@ -740,6 +740,36 @@ class Protocol:
"""
raise NotImplementedError()
@classmethod
def check_can_migrate_out(cls, user, to_user_id):
"""Raises an exception if a user can't yet migrate to a native account.
For example, if ``to_user_id`` isn't on this protocol, or if ``user`` is on
this protocol, or isn't bridged to this protocol.
If the user is ready to migrate, returns ``None``.
Subclasses may override this to add more criteria, but they should call this
implementation first.
Args:
user (models.User)
to_user_id (str)
Raises:
ValueError: if ``user`` isn't ready to migrate to this protocol yet
"""
def _error(msg):
logger.warning(msg)
raise ValueError(msg)
if cls.owns_id(to_user_id) is False:
_error(f"{to_user_id} doesn't look like an {cls.LABEL} id")
elif isinstance(user, cls):
_error(f"{user.handle_or_id()} is on {cls.PHRASE}")
elif not user.is_enabled(cls):
_error(f"{user.handle_or_id()} isn't currently bridged to {cls.PHRASE}")
@classmethod
def migrate_in(cls, user, from_user_id, **kwargs):
"""Migrates a native account in to be a bridged account.

Wyświetl plik

@ -2344,6 +2344,30 @@ class ActivityPubTest(TestCase):
with self.assertRaises(ValueError):
ActivityPub.migrate_out(self.user, 'http://in.st/to')
def test_check_can_migrate_out(self, _, mock_get, mock_post):
mock_get.return_value = self.as2_resp({
**ACTOR,
'alsoKnownAs': ['http://localhost/user.com'],
})
# shouldn't raise
ActivityPub.check_can_migrate_out(self.user, 'http://in.st/to')
def test_check_can_migrate_out_no_alias_in_to_actor(self, _, mock_get, __):
mock_get.return_value = self.as2_resp(ACTOR)
self.user.enabled_protocols = ['activitypub']
with self.assertRaises(ValueError):
ActivityPub.check_can_migrate_out(self.user, 'http://in.st/to')
mock_get.return_value = self.as2_resp({
**ACTOR,
'alsoKnownAs': ['oth', 'er'],
})
with self.assertRaises(ValueError):
ActivityPub.check_can_migrate_out(self.user, 'http://in.st/to')
class ActivityPubUtilsTest(TestCase):
def setUp(self):

Wyświetl plik

@ -1024,6 +1024,24 @@ class ProtocolTest(TestCase):
Fake.bot_follow(user)
self.assertEqual([], Fake.sent)
def test_check_can_migrate_out(self, *_):
fake = Fake(id='fake:user', enabled_protocols=['other'])
OtherFake.check_can_migrate_out(fake, 'other:user')
def test_check_can_migrate_out_bad_user_id(self, *_):
fake = Fake(id='fake:user', enabled_protocols=['other'])
with self.assertRaises(ValueError):
OtherFake.check_can_migrate_out(self.user, 'at://did:xyz')
def test_check_can_migrate_out_user_not_enabled(self, *_):
fake = Fake(id='fake:user', enabled_protocols=['efake'])
with self.assertRaises(ValueError):
OtherFake.check_can_migrate_out(fake, 'https://in.st/eve')
def test_check_can_migrate_out_same_protocol(self, *_):
fake = Fake(id='fake:user', enabled_protocols=['other'])
with self.assertRaises(ValueError):
Fake.check_can_migrate_out(fake, 'fake:eve')
class ProtocolReceiveTest(TestCase):