Skip to content

Commit

Permalink
drop User.direct
Browse files Browse the repository at this point in the history
subsumed by User.enabled_protocols. for #973
  • Loading branch information
snarfed committed Jan 7, 2025
1 parent 2128ae7 commit 1aca15e
Show file tree
Hide file tree
Showing 15 changed files with 37 additions and 172 deletions.
12 changes: 3 additions & 9 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 @[email protected] looks
# up @[email protected] via WebFinger, we'll create Users for both,
# @[email protected] 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()
Expand All @@ -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()
Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 1 addition & 4 deletions protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
5 changes: 1 addition & 4 deletions redirect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}')
Expand Down
4 changes: 2 additions & 2 deletions templates/docs.html
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ <h3 id="from-fediverse">From the fediverse</h3>

<li id="fediverse-follow-web" class="question">How do I find a bridged web site?</li>
<li class="answer">
<p>You can follow any web site, eg <a class="handle" href="https://example.com/">example.com</a>, by searching for <span class="handle">@example[email protected]</span> in your fediverse instance.</p>
<p>To follow a web site, first <a href="/web-site">enter it here</a> to make sure it's set up, then wait a minute, then search for <code>@[domain]@web.brid.gy</code> in your fediverse instance. For example, to follow <a href="https://nature.com/">nature.com</a>, search for <code>@nature[email protected]</code>.</p>
<p>Bridged web sites appear in the fediverse as either <code>@[domain]@[domain]</code>, <code>@[domain]@web.brid.gy</code>, or <code>@[domain]@fed.brid.gy</code>, depending on the fediverse server and whether the web site owner has <a href="#fediverse-enhanced">connected their domain to Bridgy Fed</a>. All bridged web sites behave the same in the fediverse; the different instances in their handles are purely cosmetic.</p>
</li>

Expand Down Expand Up @@ -579,7 +579,7 @@ <h3 id="from-bluesky">From Bluesky</h3>

<li id="bluesky-follow-web" class="question">How do I find a bridged web site?</li>
<li class="answer">
<p>To follow a web site, first <a href="#enter-web-site">enter it here</a> to make sure it's set up, then wait a minute, then <a href="https://bsky.app/search">search for it in Bluesky</a> as <code>[domain].web.brid.gy</code>. For example, <a href="https://nature.com/">nature.com</a> is bridged into Bluesky as <a href="https://bsky.app/profile/nature.com.web.brid.gy">nature.com.web.brid.gy</a>.</p>
<p>To follow a web site, first <a href="/web-site">enter it here</a> to make sure it's set up, then wait a minute, then <a href="https://bsky.app/search">search for it in Bluesky</a> as <code>[domain].web.brid.gy</code>. For example, <a href="https://nature.com/">nature.com</a> is bridged into Bluesky as <a href="https://bsky.app/profile/nature.com.web.brid.gy">nature.com.web.brid.gy</a>.</p>
</li>


Expand Down
2 changes: 1 addition & 1 deletion templates/enter_web_site.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

{% block content %}

<p class="row big">What's your web site?</p>
<p class="row big">Enter a web site to bridge:</p>

<div class="row big">
<form method="post" action=""> <!-- empty action means post to same URL -->
Expand Down
15 changes: 3 additions & 12 deletions tests/test_activitypub.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
7 changes: 0 additions & 7 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down
8 changes: 0 additions & 8 deletions tests/test_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
40 changes: 4 additions & 36 deletions tests/test_redirect.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,25 +109,17 @@ 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()

self.user.key.delete()

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
Expand All @@ -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': '<a rel="me" href="https://user.com"><span class="invisible">https://</span>user.com</a>',
}],
}, ignore=['@context', 'redirects_error'])

# TODO: is this test still useful?
def test_accept_header_across_requests(self):
self.client = app.test_client()
Expand Down
8 changes: 2 additions & 6 deletions tests/test_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -687,9 +686,6 @@ def test_username(self, *mocks):
self.user.obj.as2['url'].append('acct:[email protected]')
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
73 changes: 12 additions & 61 deletions tests/test_webfinger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:[email protected]',
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(
Expand Down Expand Up @@ -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:[email protected]')
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:[email protected]')
self.assertEqual(404, got.status_code)
for acct in ('nope.com', '[email protected]', '[email protected]',
'[email protected]', 'fake:handle:[email protected]'):
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:[email protected]')
self.assertEqual(404, got.status_code)

got = self.client.get(
subdomain = self.client.get(
f'/.well-known/webfinger?resource=acct:[email protected]')
self.assertEqual(200, got.status_code)
self.assertEqual(200, subdomain.status_code)

user_domain = self.client.get(
f'/.well-known/webfinger?resource=acct:[email protected]')
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/'):
Expand All @@ -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:[email protected]')
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:[email protected]')
Expand Down Expand Up @@ -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:[email protected]'

got = self.client.get(
'/.well-known/webfinger?resource=acct:[email protected]',
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):
Expand Down
1 change: 0 additions & 1 deletion tests/testutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading

0 comments on commit 1aca15e

Please sign in to comment.