From 8e60346dc48c0ad4f9f5a4d1bfc3067b336aceed Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Mon, 7 Nov 2022 16:26:33 -0800 Subject: [PATCH] don't strip www, m, and mobile subdomains from user domains fixes #267 --- activitypub.py | 10 ++++++---- common.py | 6 ++++-- redirect.py | 3 ++- tests/test_activitypub.py | 28 ++++++++++++++-------------- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/activitypub.py b/activitypub.py index 083a721..2cd9be0 100644 --- a/activitypub.py +++ b/activitypub.py @@ -73,7 +73,7 @@ def send(activity, inbox_url, user_domain): # required by Mastodon # https://github.com/tootsuite/mastodon/pull/14556#issuecomment-674077648 '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, headers=headers) @@ -204,7 +204,7 @@ def accept_follow(follow, follow_unwrapped): error('Follow actor requires id and inbox. Got: %s', 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)) # send AP Accept @@ -220,6 +220,8 @@ def accept_follow(follow, follow_unwrapped): 'object': followee, } } + # STATE: this fails, then we don't create a Response, since that happens in + # send_webmentions resp = send(accept, inbox, user_domain) # send webmention @@ -231,7 +233,7 @@ def accept_follow(follow, follow_unwrapped): @ndb.transactional() 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: 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) # 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)) if follower_obj: logger.info(f'Marking {follower_obj.key} as inactive') diff --git a/common.py b/common.py index 01f6321..5fe0c88 100644 --- a/common.py +++ b/common.py @@ -189,7 +189,8 @@ def send_webmentions(activity_wrapped, proxy=None, **response_props): # send webmentions and store Responses errors = [] # stores (code, body) tuples 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}') continue @@ -398,7 +399,8 @@ def redirect_unwrap(val): if val.startswith(prefix): return util.follow_redirects(val[len(prefix):]).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 val diff --git a/redirect.py b/redirect.py index df1451f..7607bb5 100644 --- a/redirect.py +++ b/redirect.py @@ -47,7 +47,8 @@ def redir(to): error(f'Expected fully qualified URL; got {to}') # 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)) for domain in domains: if domain and MagicKey.get_by_id(domain): diff --git a/tests/test_activitypub.py b/tests/test_activitypub.py index 1df0e0f..0f10676 100644 --- a/tests/test_activitypub.py +++ b/tests/test_activitypub.py @@ -89,10 +89,10 @@ FOLLOW = { 'id': 'https://mastodon.social/6d1a', 'type': 'Follow', 'actor': 'https://mastodon.social/users/swentel', - 'object': 'https://realize.be/', + 'object': 'https://www.realize.be/', } FOLLOW_WRAPPED = copy.deepcopy(FOLLOW) -FOLLOW_WRAPPED['object'] = 'http://localhost/realize.be' +FOLLOW_WRAPPED['object'] = 'http://localhost/www.realize.be' ACTOR = { '@context': 'https://www.w3.org/ns/activitystreams', 'id': FOLLOW['actor'], @@ -112,12 +112,12 @@ FOLLOW_WRAPPED_WITH_ACTOR['actor'] = FOLLOW_WITH_ACTOR['actor'] ACCEPT = { '@context': 'https://www.w3.org/ns/activitystreams', 'type': 'Accept', - 'id': 'tag:localhost:accept/realize.be/https://mastodon.social/6d1a', - 'actor': 'http://localhost/realize.be', + 'id': 'tag:localhost:accept/www.realize.be/https://mastodon.social/6d1a', + 'actor': 'http://localhost/www.realize.be', 'object': { 'type': 'Follow', '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)) 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 = [ # source actor requests_response(FOLLOW_WITH_ACTOR['actor'], @@ -349,32 +349,32 @@ class ActivityPubTest(testutil.TestCase): # check webmention 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({ - 'source': 'http://localhost/render?source=https%3A%2F%2Fmastodon.social%2F6d1a&target=https%3A%2F%2Frealize.be%2F', - 'target': 'https://realize.be/', + 'source': 'http://localhost/render?source=https%3A%2F%2Fmastodon.social%2F6d1a&target=https%3A%2F%2Fwww.realize.be%2F', + 'target': 'https://www.realize.be/', }, 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('activitypub', resp.protocol) self.assertEqual('complete', resp.status) self.assertEqual(FOLLOW_WITH_ACTOR, json_loads(resp.source_as2)) # 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(FOLLOW_WRAPPED_WITH_ACTOR, json_loads(follower.last_follow)) 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) 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) def test_inbox_undo_follow_doesnt_exist(self, mock_head, mock_get, mock_post):