don't strip www, m, and mobile subdomains from user domains

fixes #267
accept-fails-store-response
Ryan Barrett 2022-11-07 16:26:33 -08:00
rodzic cfb6653d77
commit 8e60346dc4
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
4 zmienionych plików z 26 dodań i 21 usunięć

Wyświetl plik

@ -73,7 +73,7 @@ def send(activity, inbox_url, user_domain):
# required by Mastodon # required by Mastodon
# https://github.com/tootsuite/mastodon/pull/14556#issuecomment-674077648 # https://github.com/tootsuite/mastodon/pull/14556#issuecomment-674077648
'Digest': 'SHA-256=' + b64encode(sha256(body).digest()).decode(), 'Digest': 'SHA-256=' + b64encode(sha256(body).digest()).decode(),
'Host': util.domain_from_link(inbox_url), 'Host': util.domain_from_link(inbox_url, minimize=False),
} }
return common.requests_post(inbox_url, data=body, auth=auth, return common.requests_post(inbox_url, data=body, auth=auth,
headers=headers) headers=headers)
@ -204,7 +204,7 @@ def accept_follow(follow, follow_unwrapped):
error('Follow actor requires id and inbox. Got: %s', follower) error('Follow actor requires id and inbox. Got: %s', follower)
# store Follower # store Follower
user_domain = util.domain_from_link(followee_unwrapped) user_domain = util.domain_from_link(followee_unwrapped, minimize=False)
Follower.get_or_create(user_domain, follower_id, last_follow=json_dumps(follow)) Follower.get_or_create(user_domain, follower_id, last_follow=json_dumps(follow))
# send AP Accept # send AP Accept
@ -220,6 +220,8 @@ def accept_follow(follow, follow_unwrapped):
'object': followee, 'object': followee,
} }
} }
# STATE: this fails, then we don't create a Response, since that happens in
# send_webmentions
resp = send(accept, inbox, user_domain) resp = send(accept, inbox, user_domain)
# send webmention # send webmention
@ -231,7 +233,7 @@ def accept_follow(follow, follow_unwrapped):
@ndb.transactional() @ndb.transactional()
def undo_follow(undo_unwrapped): def undo_follow(undo_unwrapped):
"""Replies to an AP Follow request with an Accept request. """Handles an AP Undo Follow request by deactivating the Follower entity.
Args: Args:
undo_unwrapped: dict, AP Undo activity with redirect URLs unwrapped undo_unwrapped: dict, AP Undo activity with redirect URLs unwrapped
@ -245,7 +247,7 @@ def undo_follow(undo_unwrapped):
error('Undo of Follow requires object with actor and object. Got: %s' % follow) error('Undo of Follow requires object with actor and object. Got: %s' % follow)
# deactivate Follower # deactivate Follower
user_domain = util.domain_from_link(followee) user_domain = util.domain_from_link(followee, minimize=False)
follower_obj = Follower.get_by_id(Follower._id(user_domain, follower)) follower_obj = Follower.get_by_id(Follower._id(user_domain, follower))
if follower_obj: if follower_obj:
logger.info(f'Marking {follower_obj.key} as inactive') logger.info(f'Marking {follower_obj.key} as inactive')

Wyświetl plik

@ -189,7 +189,8 @@ def send_webmentions(activity_wrapped, proxy=None, **response_props):
# send webmentions and store Responses # send webmentions and store Responses
errors = [] # stores (code, body) tuples errors = [] # stores (code, body) tuples
for target in targets: for target in targets:
if util.domain_from_link(target) == util.domain_from_link(source): if (util.domain_from_link(target, minimize=False) ==
util.domain_from_link(source, minimize=False)):
logger.info(f'Skipping same-domain webmention from {source} to {target}') logger.info(f'Skipping same-domain webmention from {source} to {target}')
continue continue
@ -398,7 +399,8 @@ def redirect_unwrap(val):
if val.startswith(prefix): if val.startswith(prefix):
return util.follow_redirects(val[len(prefix):]).url return util.follow_redirects(val[len(prefix):]).url
elif val.startswith(request.host_url): elif val.startswith(request.host_url):
domain = util.domain_from_link(urllib.parse.urlparse(val).path.strip('/')) domain = util.domain_from_link(urllib.parse.urlparse(val).path.strip('/'),
minimize=False)
return util.follow_redirects(domain).url return util.follow_redirects(domain).url
return val return val

Wyświetl plik

@ -47,7 +47,8 @@ def redir(to):
error(f'Expected fully qualified URL; got {to}') error(f'Expected fully qualified URL; got {to}')
# check that we've seen this domain before so we're not an open redirect # check that we've seen this domain before so we're not an open redirect
domains = set((util.domain_from_link(to), domains = set((util.domain_from_link(to, minimize=True),
util.domain_from_link(to, minimize=False),
urllib.parse.urlparse(to).hostname)) urllib.parse.urlparse(to).hostname))
for domain in domains: for domain in domains:
if domain and MagicKey.get_by_id(domain): if domain and MagicKey.get_by_id(domain):

Wyświetl plik

@ -89,10 +89,10 @@ FOLLOW = {
'id': 'https://mastodon.social/6d1a', 'id': 'https://mastodon.social/6d1a',
'type': 'Follow', 'type': 'Follow',
'actor': 'https://mastodon.social/users/swentel', 'actor': 'https://mastodon.social/users/swentel',
'object': 'https://realize.be/', 'object': 'https://www.realize.be/',
} }
FOLLOW_WRAPPED = copy.deepcopy(FOLLOW) FOLLOW_WRAPPED = copy.deepcopy(FOLLOW)
FOLLOW_WRAPPED['object'] = 'http://localhost/realize.be' FOLLOW_WRAPPED['object'] = 'http://localhost/www.realize.be'
ACTOR = { ACTOR = {
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': FOLLOW['actor'], 'id': FOLLOW['actor'],
@ -112,12 +112,12 @@ FOLLOW_WRAPPED_WITH_ACTOR['actor'] = FOLLOW_WITH_ACTOR['actor']
ACCEPT = { ACCEPT = {
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Accept', 'type': 'Accept',
'id': 'tag:localhost:accept/realize.be/https://mastodon.social/6d1a', 'id': 'tag:localhost:accept/www.realize.be/https://mastodon.social/6d1a',
'actor': 'http://localhost/realize.be', 'actor': 'http://localhost/www.realize.be',
'object': { 'object': {
'type': 'Follow', 'type': 'Follow',
'actor': 'https://mastodon.social/users/swentel', 'actor': 'https://mastodon.social/users/swentel',
'object': 'http://localhost/realize.be', 'object': 'http://localhost/www.realize.be',
} }
} }
@ -324,7 +324,7 @@ class ActivityPubTest(testutil.TestCase):
self.assertEqual(LIKE_WITH_ACTOR, json_loads(resp.source_as2)) self.assertEqual(LIKE_WITH_ACTOR, json_loads(resp.source_as2))
def test_inbox_follow_accept(self, mock_head, mock_get, mock_post): def test_inbox_follow_accept(self, mock_head, mock_get, mock_post):
mock_head.return_value = requests_response(url='https://realize.be/') mock_head.return_value = requests_response(url='https://www.realize.be/')
mock_get.side_effect = [ mock_get.side_effect = [
# source actor # source actor
requests_response(FOLLOW_WITH_ACTOR['actor'], requests_response(FOLLOW_WITH_ACTOR['actor'],
@ -349,32 +349,32 @@ class ActivityPubTest(testutil.TestCase):
# check webmention # check webmention
args, kwargs = mock_post.call_args_list[1] args, kwargs = mock_post.call_args_list[1]
self.assertEqual(('https://realize.be/webmention',), args) self.assertEqual(('https://www.realize.be/webmention',), args)
self.assertEqual({ self.assertEqual({
'source': 'http://localhost/render?source=https%3A%2F%2Fmastodon.social%2F6d1a&target=https%3A%2F%2Frealize.be%2F', 'source': 'http://localhost/render?source=https%3A%2F%2Fmastodon.social%2F6d1a&target=https%3A%2F%2Fwww.realize.be%2F',
'target': 'https://realize.be/', 'target': 'https://www.realize.be/',
}, kwargs['data']) }, kwargs['data'])
resp = Response.get_by_id('https://mastodon.social/6d1a https://realize.be/') resp = Response.get_by_id('https://mastodon.social/6d1a https://www.realize.be/')
self.assertEqual('in', resp.direction) self.assertEqual('in', resp.direction)
self.assertEqual('activitypub', resp.protocol) self.assertEqual('activitypub', resp.protocol)
self.assertEqual('complete', resp.status) self.assertEqual('complete', resp.status)
self.assertEqual(FOLLOW_WITH_ACTOR, json_loads(resp.source_as2)) self.assertEqual(FOLLOW_WITH_ACTOR, json_loads(resp.source_as2))
# check that we stored a Follower object # check that we stored a Follower object
follower = Follower.get_by_id('realize.be %s' % (FOLLOW['actor'])) follower = Follower.get_by_id('www.realize.be %s' % (FOLLOW['actor']))
self.assertEqual('active', follower.status) self.assertEqual('active', follower.status)
self.assertEqual(FOLLOW_WRAPPED_WITH_ACTOR, json_loads(follower.last_follow)) self.assertEqual(FOLLOW_WRAPPED_WITH_ACTOR, json_loads(follower.last_follow))
def test_inbox_undo_follow(self, mock_head, mock_get, mock_post): def test_inbox_undo_follow(self, mock_head, mock_get, mock_post):
mock_head.return_value = requests_response(url='https://realize.be/') mock_head.return_value = requests_response(url='https://www.realize.be/')
Follower(id=Follower._id('realize.be', FOLLOW['actor'])).put() Follower(id=Follower._id('www.realize.be', FOLLOW['actor'])).put()
got = self.client.post('/foo.com/inbox', json=UNDO_FOLLOW_WRAPPED) got = self.client.post('/foo.com/inbox', json=UNDO_FOLLOW_WRAPPED)
self.assertEqual(200, got.status_code) self.assertEqual(200, got.status_code)
follower = Follower.get_by_id('realize.be %s' % FOLLOW['actor']) follower = Follower.get_by_id('www.realize.be %s' % FOLLOW['actor'])
self.assertEqual('inactive', follower.status) self.assertEqual('inactive', follower.status)
def test_inbox_undo_follow_doesnt_exist(self, mock_head, mock_get, mock_post): def test_inbox_undo_follow_doesnt_exist(self, mock_head, mock_get, mock_post):