From 2cd127bb5ac6e61496b0834ee9bf3d38aabfbe0d Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Mon, 28 Oct 2024 21:23:11 -0700 Subject: [PATCH] /update-profile: for Web users, try to set ATProto handle to domain for #826 --- atproto.py | 1 + pages.py | 32 ++++++++++++++++++++------------ tests/test_dms.py | 6 +++--- tests/test_pages.py | 10 ++++++++-- tests/testutil.py | 14 +++++++------- 5 files changed, 39 insertions(+), 24 deletions(-) diff --git a/atproto.py b/atproto.py index 4ed80d70..e8c5b2ae 100644 --- a/atproto.py +++ b/atproto.py @@ -507,6 +507,7 @@ def set_username(to_cls, user, username): logger.info(f'repo for {did} is tombstoned, giving up') return False + logger.info(f'Setting ATProto handle for {user.key.id()} to {username}') repo.callback = lambda _: common.create_task(queue='atproto-commit') did.update_plc(did=copy_did, handle=username, signing_key=repo.signing_key, rotation_key=repo.rotation_key, diff --git a/pages.py b/pages.py index a054ce96..33e25296 100644 --- a/pages.py +++ b/pages.py @@ -17,7 +17,6 @@ canonicalize_request_domain, error, flash, - redirect, ) import requests import werkzeug.exceptions @@ -27,6 +26,7 @@ import common from common import CACHE_CONTROL, DOMAIN_RE from flask_app import app +from flask import redirect import ids from models import fetch_objects, fetch_page, Follower, Object, PAGE_SIZE, PROTOCOLS from protocol import Protocol @@ -169,27 +169,35 @@ def notifications(protocol, id): def update_profile(protocol, id): user = load_user(protocol, id) link = f'{user.handle_or_id()}' + redir = redirect(user.user_page_path(), code=302) try: user.reload_profile() except (requests.RequestException, werkzeug.exceptions.HTTPException) as e: _, msg = util.interpret_http_exception(e) flash(f"Couldn't update profile for {link}: {msg}") - return redirect(user.user_page_path(), code=302) + return redir - if user.obj: - common.create_task(queue='receive', obj_id=user.obj_key.id(), - authed_as=user.key.id()) - flash(f'Updating profile from {link}...') + if not user.obj: + flash(f"Couldn't update profile for {link}") + return redir + + common.create_task(queue='receive', obj_id=user.obj_key.id(), + authed_as=user.key.id()) + flash(f'Updating profile from {link}...') - if user.status and user.LABEL == 'web': + if user.LABEL == 'web': + if user.status: logger.info(f'Disabling web user {user.key.id()}') user.delete() - - else: - flash(f"Couldn't update profile for {link}") - - return redirect(user.user_page_path(), code=302) + else: + for label in list(user.DEFAULT_ENABLED_PROTOCOLS) + user.enabled_protocols: + try: + PROTOCOLS[label].set_username(user, id) + except (ValueError, RuntimeError, NotImplementedError) as e: + pass + + return redir @app.get(f'///') diff --git a/tests/test_dms.py b/tests/test_dms.py index bbea932c..3c5bbc04 100644 --- a/tests/test_dms.py +++ b/tests/test_dms.py @@ -333,7 +333,7 @@ def test_receive_username(self): obj = Object(our_as1=DM_EFAKE_ALICE_SET_USERNAME_OTHER) self.assertEqual(('OK', 200), receive(from_user=alice, obj=obj)) self.assert_replied(OtherFake, alice, '?', ALICE_USERNAME_CONFIRMATION_CONTENT) - self.assertEqual({OtherFake: 'new-handle'}, alice.usernames) + self.assertEqual({'efake:alice': 'new-handle'}, OtherFake.usernames) def test_receive_username_not_implemented(self): self.make_user(id='fa.brid.gy', cls=Web) @@ -357,7 +357,7 @@ def test_receive_username_fails(self, _): obj = Object(our_as1=DM_EFAKE_ALICE_SET_USERNAME_OTHER) self.assertEqual(('OK', 200), receive(from_user=alice, obj=obj)) self.assert_replied(OtherFake, alice, '?', 'nopey') - self.assertEqual({}, alice.usernames) + self.assertEqual({}, OtherFake.usernames) def test_receive_help(self): self.make_user(id='other.brid.gy', cls=Web) @@ -369,4 +369,4 @@ def test_receive_help(self): }) self.assertEqual(('OK', 200), receive(from_user=alice, obj=obj)) self.assert_replied(OtherFake, alice, '?', "

Hi! I'm a friendly bot") - self.assertEqual({}, alice.usernames) + self.assertEqual({}, OtherFake.usernames) diff --git a/tests/test_pages.py b/tests/test_pages.py index 52c8416e..f86bea41 100644 --- a/tests/test_pages.py +++ b/tests/test_pages.py @@ -13,7 +13,7 @@ from requests import ConnectionError # import first so that Fake is defined before URL routes are registered -from .testutil import Fake, TestCase, ACTOR, COMMENT, MENTION, NOTE +from .testutil import Fake, OtherFake, TestCase, ACTOR, COMMENT, MENTION, NOTE from activitypub import ActivityPub from atproto import ATProto @@ -245,7 +245,11 @@ def test_update_profile_receive_task(self, mock_create_task): @patch('requests.get', return_value=ACTOR_HTML_RESP) def test_update_profile_web(self, mock_get): - self.user.obj.copies = [Target(protocol='fake', uri='fa:profile:web:user.com')] + self.user.obj.copies = [ + Target(protocol='fake', uri='fa:profile:web:user.com'), + Target(protocol='other', uri='other:profile:web:user.com'), + ] + self.user.enabled_protocols = ['other'] self.user.obj.put() Follower.get_or_create(from_=self.make_user('fake:user', cls=Fake), to=self.user) @@ -270,6 +274,8 @@ def test_update_profile_web(self, mock_get): 'object': actor_as1, })], Fake.sent) + self.assertEqual({'user.com': 'user.com'}, OtherFake.usernames) + @patch('requests.get', return_value=requests_response( ACTOR_HTML.replace('Ms. ☕ Baz', 'Ms. ☕ Baz #nobridge'), url='https://user.com/')) diff --git a/tests/testutil.py b/tests/testutil.py index 855b9efa..ac2e2303 100644 --- a/tests/testutil.py +++ b/tests/testutil.py @@ -92,12 +92,8 @@ class Fake(User, protocol.Protocol): fetched = [] created_for = [] - # instance attr, dict mapping Protocol class to str username - usernames = None - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.usernames = {} + # maps str user id to str username + usernames = {} @ndb.ComputedProperty def handle(self): @@ -222,6 +218,7 @@ class OtherFake(Fake): sent = [] fetched = [] created_for = [] + usernames = {} @classmethod def target_for(cls, obj, shared=False): @@ -230,7 +227,8 @@ def target_for(cls, obj, shared=False): @classmethod def set_username(cls, user, username): - user.usernames[cls] = username + logger.info(f'Setting custom username for {user.key.id()} in {cls.LABEL} to {username}') + cls.usernames[user.key.id()] = username class ExplicitFake(Fake): @@ -245,6 +243,7 @@ class ExplicitFake(Fake): sent = [] fetched = [] created_for = [] + usernames = {} # import other modules that register Flask handlers *after* Fake is defined @@ -305,6 +304,7 @@ def setUp(self): cls.sent = [] cls.fetched = [] cls.created_for = [] + cls.usernames = {} # make random test data deterministic arroba.util._clockid = 17