diff --git a/models.py b/models.py index 84bf3e49..e6e4c44d 100644 --- a/models.py +++ b/models.py @@ -191,12 +191,6 @@ class User(StringIdModel, metaclass=ProtocolUserMeta): # TODO: switch to using Object.copies on the user profile object? copies = ndb.StructuredProperty(Target, repeated=True) - # whether this user signed up or otherwise explicitly, deliberately - # interacted with Bridgy Fed. For example, if fediverse user @a@b.com looks - # up @foo.com@fed.brid.gy via WebFinger, we'll create Users for both, - # @a@b.com will be direct, foo.com will not. - direct = ndb.BooleanProperty(default=False) - # these are for ActivityPub HTTP Signatures public_exponent = ndb.StringProperty() private_exponent = ndb.StringProperty() @@ -219,6 +213,7 @@ class User(StringIdModel, metaclass=ProtocolUserMeta): # `existing` attr is set by get_or_create # OLD. some stored entities still have these; do not reuse. + # direct = ndb.BooleanProperty(default=False) # actor_as2 = JsonProperty() # protocol-specific state # atproto_notifs_indexed_at = ndb.TextProperty() @@ -310,11 +305,10 @@ def _run(): # TODO: propagate more fields? changed = False - for field in ['direct', 'obj', 'obj_key']: + for field in ['obj', 'obj_key']: old_val = getattr(user, field, None) new_val = kwargs.get(field) - if ((old_val is None and new_val is not None) - or (field == 'direct' and not old_val and new_val)): + if old_val is None and new_val is not None: setattr(user, field, new_val) changed = True diff --git a/pages.py b/pages.py index 4192184c..904a71b7 100644 --- a/pages.py +++ b/pages.py @@ -84,8 +84,8 @@ def load_user(protocol, id): elif user and id != user.key.id(): # use_instead redirect error('', status=302, location=user.user_page_path()) - if (user and not user.status - and (user.direct or user.enabled_protocols or cls.ABBREV == 'web')): + if user and not user.status and (user.enabled_protocols + or user.DEFAULT_ENABLED_PROTOCOLS): assert not user.use_instead return user diff --git a/protocol.py b/protocol.py index 5c5953e1..f4169472 100644 --- a/protocol.py +++ b/protocol.py @@ -1102,10 +1102,7 @@ def handle_follow(from_cls, obj): logger.info(f'Skipping invalid {from_cls.LABEL} user key: {from_id}') continue - # If followee user is already direct, follower may not know they're - # interacting with a bridge. if followee user is indirect though, - # follower should know, so they're direct. - to_user = to_cls.get_or_create(id=to_key.id(), obj=to_obj, direct=False, + to_user = to_cls.get_or_create(id=to_key.id(), obj=to_obj, allow_opt_out=True) follower_obj = Follower.get_or_create(to=to_user, from_=from_user, follow=obj.key, status='active') diff --git a/redirect.py b/redirect.py index fe91d2a3..eb586746 100644 --- a/redirect.py +++ b/redirect.py @@ -109,11 +109,8 @@ def redir(to): if not obj or obj.deleted: return f'Object not found: {to}', 404 - # TODO: do this for other protocols too? if proto == Web and not web_user: - web_user = Web.get_or_create(util.domain_from_link(to), direct=False, obj=obj) - if not web_user: - return f'Object not found: {to}', 404 + return f'Object not found: {to}', 404 ret = ActivityPub.convert(obj, from_user=web_user) # logger.info(f'Returning: {json_dumps(ret, indent=2)}') diff --git a/templates/docs.html b/templates/docs.html index 37ef6f22..23a65510 100644 --- a/templates/docs.html +++ b/templates/docs.html @@ -428,7 +428,7 @@

From the fediverse

  • How do I find a bridged web site?
  • -

    You can follow any web site, eg example.com, by searching for @example.com@web.brid.gy in your fediverse instance.

    +

    To follow a web site, first enter it here to make sure it's set up, then wait a minute, then search for @[domain]@web.brid.gy in your fediverse instance. For example, to follow nature.com, search for @nature.com@web.brid.gy.

    Bridged web sites appear in the fediverse as either @[domain]@[domain], @[domain]@web.brid.gy, or @[domain]@fed.brid.gy, depending on the fediverse server and whether the web site owner has connected their domain to Bridgy Fed. All bridged web sites behave the same in the fediverse; the different instances in their handles are purely cosmetic.

  • @@ -579,7 +579,7 @@

    From Bluesky

  • How do I find a bridged web site?
  • -

    To follow a web site, first enter it here to make sure it's set up, then wait a minute, then search for it in Bluesky as [domain].web.brid.gy. For example, nature.com is bridged into Bluesky as nature.com.web.brid.gy.

    +

    To follow a web site, first enter it here to make sure it's set up, then wait a minute, then search for it in Bluesky as [domain].web.brid.gy. For example, nature.com is bridged into Bluesky as nature.com.web.brid.gy.

  • diff --git a/templates/enter_web_site.html b/templates/enter_web_site.html index 53f1d7b7..40e35681 100644 --- a/templates/enter_web_site.html +++ b/templates/enter_web_site.html @@ -2,7 +2,7 @@ {% block content %} -

    What's your web site?

    +

    Enter a web site to bridge:

    diff --git a/tests/test_activitypub.py b/tests/test_activitypub.py index 1929b243..488fa5b8 100644 --- a/tests/test_activitypub.py +++ b/tests/test_activitypub.py @@ -1123,12 +1123,8 @@ def test_inbox_like(self, mock_head, mock_get, mock_post): type='like', ) - def test_inbox_like_indirect_user_creates_user(self, mock_get, *_): - self.user.direct = False - self.user.put() - + def test_inbox_like_creates_user(self, mock_get, *_): mock_get.return_value = self.as2_resp(LIKE_ACTOR) - self.test_inbox_like() self.assert_user(ActivityPub, 'https://mas.to/me', obj_as2=LIKE_ACTOR) @@ -1254,10 +1250,6 @@ def test_inbox_follow_accept_webmention_fails(self, mock_head, mock_get, def _test_inbox_follow_accept(self, follow_as2, accept_as2, mock_head, mock_get, mock_post, inbox_path='/user.com/inbox'): - # this should makes us make the follower ActivityPub as direct=True - self.user.direct = False - self.user.put() - mock_head.return_value = requests_response(url='https://user.com/') mock_get.side_effect = [ self.as2_resp(ACTOR), # source actor @@ -1297,9 +1289,8 @@ def _test_inbox_follow_accept(self, follow_as2, accept_as2, mock_head, Follower.query().fetch(), ignore=['created', 'updated']) - self.assert_user(ActivityPub, 'https://mas.to/users/swentel', - obj_as2=ACTOR, direct=False) - self.assert_user(Web, 'user.com', direct=False, last_webmention_in=NOW, + self.assert_user(ActivityPub, 'https://mas.to/users/swentel', obj_as2=ACTOR) + self.assert_user(Web, 'user.com', last_webmention_in=NOW, has_hcard=True, has_redirects=True) def test_inbox_follow_use_instead_strip_www(self, mock_head, mock_get, mock_post): diff --git a/tests/test_models.py b/tests/test_models.py index 7f6fe5d4..21e2453e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -53,7 +53,6 @@ def test_get_by_id_opted_out(self): def test_get_or_create(self): user = Fake.get_or_create('fake:user') - assert not user.direct assert not user.existing assert user.mod assert user.public_exponent @@ -63,12 +62,6 @@ def test_get_or_create(self): assert user.public_pem() assert user.private_pem() - # direct should get set even if the user exists - same = Fake.get_or_create('fake:user', direct=True) - assert same.existing - user.direct = True - self.assert_entities_equal(same, user, ignore=['updated']) - def test_get_or_create_existing_merge_enabled_protocols(self): self.user.enabled_protocols = ['fake'] self.user.put() diff --git a/tests/test_pages.py b/tests/test_pages.py index fd9141d7..1031e2cb 100644 --- a/tests/test_pages.py +++ b/tests/test_pages.py @@ -117,14 +117,6 @@ def test_user_not_found(self): got = self.client.get('/web/bar.com') self.assert_equals(404, got.status_code) - def test_user_not_direct(self): - got = self.client.get('/web/user.com') - self.assert_equals(200, got.status_code) - - fake = self.make_user('http://fo/o', cls=ActivityPub, direct=False) - got = self.client.get('/ap/@o@fo') - self.assert_equals(404, got.status_code) - def test_user_opted_out(self): self.user.obj.our_as1 = {'summary': '#nobridge'} self.user.obj.put() diff --git a/tests/test_redirect.py b/tests/test_redirect.py index ce71a052..d247171f 100644 --- a/tests/test_redirect.py +++ b/tests/test_redirect.py @@ -109,11 +109,7 @@ def test_as2(self): def test_as2_ld(self): self._test_as2(as2.CONTENT_TYPE_LD_PROFILE) - @patch('requests.get', side_effect=[ - ACTOR_HTML_RESP, # h-card fetch - requests_response(status=404), # webfinger - ]) - def test_as2_creates_user(self, _): + def test_as2_missing_user(self): Object(id='https://user.com/repost', source_protocol='web', as2=REPOST_AS2).put() @@ -121,13 +117,9 @@ def test_as2_creates_user(self, _): resp = self.client.get('/r/https://user.com/repost', headers={'Accept': as2.CONTENT_TYPE_LD_PROFILE}) - self.assertEqual(200, resp.status_code, resp.get_data(as_text=True)) - self.assert_equals(REPOST_AS2, resp.json) + self.assertEqual(404, resp.status_code, resp.get_data(as_text=True)) self.assertEqual('Accept', resp.headers['Vary']) - self.assert_user(Web, 'user.com', has_hcard=True, has_redirects=False, - direct=False, ignore=['redirects_error']) - @patch('requests.get') def test_as2_fetch_post(self, mock_get): mock_get.return_value = TOOT_AS2 # from Protocol.for_id @@ -141,40 +133,16 @@ def test_as2_fetch_post(self, mock_get): @patch('requests.get', side_effect=[ requests_response(ACTOR_HTML, url='https://user.com/'), # AS2 fetch requests_response(ACTOR_HTML, url='https://user.com/'), # web fetch - requests_response(ACTOR_HTML, url='https://user.com/'), # h-card fetch - requests_response(status=404), # webfinger ]) - def test_as2_no_user_fetch_homepage(self, mock_get): + def test_as2_no_user(self, _): self.user.key.delete() self.user.obj_key.delete() resp = self.client.get('/r/https://user.com/', headers={'Accept': as2.CONTENT_TYPE_LD_PROFILE}) - self.assertEqual(200, resp.status_code, resp.get_data(as_text=True)) + self.assertEqual(404, resp.status_code, resp.get_data(as_text=True)) self.assertEqual('Accept', resp.headers['Vary']) - self.assert_equals({ - **ACTOR_BASE_FULL, - 'discoverable': True, - 'indexable': True, - }, resp.json, ignore=['@context', 'alsoKnownAs', 'endpoints', 'followers', - 'following', 'publicKey', 'summary']) - - self.assert_user(Web, 'user.com', has_hcard=True, has_redirects=False, - direct=False, obj_as2={ - 'type': 'Person', - 'id': 'https://user.com/', - 'url': 'https://user.com/', - 'name': 'Ms. ☕ Baz', - 'discoverable': True, - 'indexable': True, - 'attachment': [{ - 'type': 'PropertyValue', - 'name': 'Ms. ☕ Baz', - 'value': 'user.com', - }], - }, ignore=['@context', 'redirects_error']) - # TODO: is this test still useful? def test_accept_header_across_requests(self): self.client = app.test_client() diff --git a/tests/test_web.py b/tests/test_web.py index c6d39f8f..50ce2259 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -673,7 +673,6 @@ def test_username(self, *mocks): 'url': ['bar'], 'preferredUsername': 'baz', }) - self.user.direct = True self.assertEqual('user.com', self.user.username()) # bad acct: URI, util.parse_acct_uri raises ValueError @@ -687,9 +686,6 @@ def test_username(self, *mocks): self.user.obj.as2['url'].append('acct:alice@user.com') self.assertEqual('alice', self.user.username()) - self.user.direct = False - self.assertEqual('user.com', self.user.username()) - @patch('oauth_dropins.webutil.appengine_config.tasks_client.create_task') def test_make_task(self, mock_create_task, mock_get, mock_post): common.RUN_TASKS_INLINE = False @@ -1820,7 +1816,7 @@ def test_update_profile(self, mock_get, mock_post): 'discoverable': True, 'indexable': True, } - self.assert_user(Web, 'user.com', obj_as2=expected_actor_as2, direct=True, + self.assert_user(Web, 'user.com', obj_as2=expected_actor_as2, has_redirects=True) # homepage object @@ -1896,7 +1892,7 @@ def test_update_profile_homepage_no_mf2_or_metaformats(self, mock_get, mock_post self.assert_deliveries(mock_post, ('https://inbox',), update_as2) # updated Web user - self.assert_user(Web, 'user.com', direct=True, has_redirects=True, obj_as2={ + self.assert_user(Web, 'user.com', has_redirects=True, obj_as2={ '@context': [ 'https://www.w3.org/ns/activitystreams', as2.DISCOVERABLE_INDEXABLE_CONTEXT, diff --git a/tests/test_webfinger.py b/tests/test_webfinger.py index 9fc6a461..0bc44ff3 100644 --- a/tests/test_webfinger.py +++ b/tests/test_webfinger.py @@ -238,19 +238,6 @@ def test_user_infer_protocol_resource_overrides_request(self): self.assertEqual('application/jrd+json', got.headers['Content-Type']) self.assert_equals(WEBFINGER_FAKE_FA_BRID_GY, got.json) - @patch.object(Fake, 'DEFAULT_ENABLED_PROTOCOLS', ['activitypub']) - def test_handle_new_user(self): - self.assertIsNone(Fake.get_by_id('fake:user')) - - Fake.fetchable['fake:user'] = {'id': 'fake:user'} - - got = self.client.get( - '/.well-known/webfinger?resource=acct:fake:handle:user@fake.brid.gy', - base_url='https://fed.brid.gy/', - headers={'Accept': 'application/json'}) - self.assertEqual(200, got.status_code, got.get_data(as_text=True)) - self.assert_equals(WEBFINGER_FAKE_FA_BRID_GY, got.json) - def test_urlencoded(self): """https://github.com/snarfed/bridgy-fed/issues/535""" got = self.client.get( @@ -301,29 +288,24 @@ def test_custom_username(self): }, got.json) def test_missing_user(self): - got = self.client.get(f'/.well-known/webfinger?resource=acct:nope.com@nope.com') - self.assertEqual(404, got.status_code) - - got = self.client.get(f'/.well-known/webfinger?resource=acct:nope.com') - self.assertEqual(404, got.status_code) - - def test_indirect_user_not_on_bridgy_fed_subdomain(self): - self.user.direct = False - self.user.put() - got = self.client.get(f'/.well-known/webfinger?resource=acct:user.com@user.com') - self.assertEqual(404, got.status_code) + for acct in ('nope.com', 'nope.com@nope.com', 'nope.com@web.brid.gy', + 'nope.com@fed.brid.gy', 'fake:handle:user@fake.brid.gy'): + got = self.client.get(f'/.well-known/webfinger?resource=acct:{acct}') + self.assertEqual(404, got.status_code) def test_no_redirects_user_not_on_bridgy_fed_subdomain(self): self.user.has_redirects = False self.user.put() - got = self.client.get( - f'/.well-known/webfinger?resource=acct:user.com@user.com') - self.assertEqual(404, got.status_code) - - got = self.client.get( + subdomain = self.client.get( f'/.well-known/webfinger?resource=acct:user.com@web.brid.gy') - self.assertEqual(200, got.status_code) + self.assertEqual(200, subdomain.status_code) + + user_domain = self.client.get( + f'/.well-known/webfinger?resource=acct:user.com@user.com') + self.assertEqual(200, user_domain.status_code) + self.assertEqual(subdomain.get_data(as_text=True), + user_domain.get_data(as_text=True)) def test_user_not_custom_username(self): for base_url in (None, 'https://web.brid.gy/', 'https://fed.brid.gy/'): @@ -333,12 +315,6 @@ def test_user_not_custom_username(self): base_url=base_url) self.assertEqual(404, got.status_code) - def test_missing_user_web_subdomain(self): - self.user.direct = False - self.user.put() - got = self.client.get(f'/.well-known/webfinger?resource=acct:foo@bar.com') - self.assertEqual(404, got.status_code) - def test_protocol_not_enabled(self): self.make_user('efake:user', cls=ExplicitFake) got = self.client.get(f'/.well-known/webfinger?resource=acct:efake:user@efake.brid.gy') @@ -379,31 +355,6 @@ class NoHandle(Fake): finally: PROTOCOLS.pop('nohandle') - @patch('requests.get') - def test_create_user(self, mock_get): - self.user.key.delete() - self.user.obj_key.delete() - - hcard = return_value = requests_response(test_web.ACTOR_HTML, - url='https://user.com/') - mock_get.side_effect = [ - hcard, - requests_response(status=404), - hcard, - ] - expected = copy.deepcopy(WEBFINGER_NO_HCARD) - expected['subject'] = 'acct:user.com@web.brid.gy' - - got = self.client.get( - '/.well-known/webfinger?resource=acct:user.com@web.brid.gy', - headers={'Accept': 'application/json'}, - base_url='https://web.brid.gy/') - self.assertEqual(200, got.status_code) - self.assertEqual(expected, got.json) - - user = Web.get_by_id('user.com') - assert not user.direct - # skip _pre_put_hook since it doesn't allow internal domains @patch.object(Web, '_pre_put_hook', new=lambda self: None) def test_protocol_bot_user(self): diff --git a/tests/testutil.py b/tests/testutil.py index 1dde4132..37cf5765 100644 --- a/tests/testutil.py +++ b/tests/testutil.py @@ -415,7 +415,6 @@ def make_user(self, id, cls, **kwargs): obj_mf2 = copy.deepcopy(kwargs.pop('obj_mf2', None)) obj_id = copy.deepcopy(kwargs.pop('obj_id', None)) - kwargs.setdefault('direct', True) if cls == Web and not obj_mf2: kwargs.setdefault('last_webmention_in', testutil.NOW) diff --git a/web.py b/web.py index 443668a7..be219c88 100644 --- a/web.py +++ b/web.py @@ -244,7 +244,7 @@ def username(self): """ id = self.key.id() - if self.obj and self.obj.as1 and self.direct: + if self.obj and self.obj.as1: for url in (util.get_list(self.obj.as1, 'url') + util.get_list(self.obj.as1, 'urls')): url = url.get('value') if isinstance(url, dict) else url @@ -296,9 +296,8 @@ def verify(self): if resp.ok and self.is_web_url(resp.url): logger.info(f'{root_site} serves ok ; using {root} instead') root_user = Web.get_or_create( - root, - enabled_protocols=self.enabled_protocols, - allow_opt_out=True, direct=self.direct) + root, enabled_protocols=self.enabled_protocols, + allow_opt_out=True) self.use_instead = root_user.key self.put() return root_user.verify() @@ -705,7 +704,7 @@ def check_web_site(): try: user = Web.get_or_create(domain, enabled_protocols=['atproto'], - propagate=True, direct=True, reload=True, verify=True) + propagate=True, reload=True, verify=True) except BaseException as e: code, body = util.interpret_http_exception(e) if code: diff --git a/webfinger.py b/webfinger.py index 2575a477..b200186b 100644 --- a/webfinger.py +++ b/webfinger.py @@ -55,7 +55,6 @@ def template_vars(self): if resource in ('', '/', f'acct:{host}', f'acct:@{host}'): error('Expected other domain, not *.brid.gy') - allow_indirect = False cls = None try: username, server = util.parse_acct_uri(resource) @@ -63,13 +62,11 @@ def template_vars(self): cls = Protocol.for_bridgy_subdomain(id, fed='web') if cls: id = username - allow_indirect = True except ValueError: id = username = server = urlparse(resource).netloc or resource if id == PRIMARY_DOMAIN or id in PROTOCOL_DOMAINS: cls = Web - allow_indirect = True elif not cls: cls = Protocol.for_request(fed='web') @@ -91,19 +88,10 @@ def template_vars(self): logger.info(f'Protocol {cls.LABEL}, user id {id}') - # only allow indirect/no-redirects users if this id is "on" a brid.gy - # subdomain, eg user.com@bsky.brid.gy but not user.com@user.com - if allow_indirect: - user = cls.get_or_create(id) - else: - user = cls.get_by_id(id) - if user and not user.direct: - error(f"{user.key.id()} hasn't signed up yet", status=404) - + user = cls.get_by_id(id) if (not user or not user.is_enabled(activitypub.ActivityPub) - or (cls == Web and username not in (user.key.id(), user.username())) - or (cls == Web and not allow_indirect and not user.has_redirects)): + or (cls == Web and username not in (user.key.id(), user.username()))): error(f'No {cls.LABEL} user found for {id}', status=404) ap_handle = user.handle_as('activitypub')