def test_bad_source_url(self, mock_get, mock_post): got = application.get_response('/webmention', method='POST', body=b'') self.assertEqual(400, got.status_int) mock_get.side_effect = ValueError('foo bar') got = application.get_response('/webmention', method='POST', body=urlencode({ 'source': 'bad' }).encode()) self.assertEqual(400, got.status_int)
def test_render_errors(self): for source, target in ('', ''), ('abc', ''), ('', 'xyz'): resp = application.get_response('/render?source=%s&target=%s' % (source, target)) self.assertEqual(400, resp.status_int, resp.body) # no Response resp = application.get_response('/render?source=abc&target=xyz') self.assertEqual(404, resp.status_int) # no source data Response(id='abc xyz').put() resp = application.get_response('/render?source=abc&target=xyz') self.assertEqual(404, resp.status_int)
def test_render_atom(self): Response(id='abc xyz', source_atom=self.atom).put() resp = application.get_response('/render?source=abc&target=xyz') self.assertEqual(200, resp.status_int) self.assert_multiline_equals(self.html, resp.body.decode(), ignore_blanks=True)
def _test_inbox_mention(self, as2, mock_head, mock_get, mock_post): mock_head.return_value = requests_response(url='http://target') mock_get.return_value = requests_response( '<html><head><link rel="webmention" href="/webmention"></html>') mock_post.return_value = requests_response() got = application.get_response('/foo.com/inbox', method='POST', body=json_dumps(as2).encode()) self.assertEqual(200, got.status_int, got.body) mock_get.assert_called_once_with('http://target/', headers=common.HEADERS, verify=False) expected_headers = copy.deepcopy(common.HEADERS) expected_headers['Accept'] = '*/*' mock_post.assert_called_once_with( 'http://target/webmention', data={ 'source': 'http://localhost/render?source=http%3A%2F%2Fthis%2Fmention&target=http%3A%2F%2Ftarget%2F', 'target': 'http://target/', }, allow_redirects=False, headers=expected_headers, verify=False) resp = Response.get_by_id('http://this/mention http://target/') self.assertEqual('in', resp.direction) self.assertEqual('activitypub', resp.protocol) self.assertEqual('complete', resp.status) self.assertEqual(self.handler.redirect_unwrap(as2), json_loads(resp.source_as2))
def test_bad_inner_xml(self, *mocks): slap = magicsigs.magic_envelope('not xml', common.CONTENT_TYPE_ATOM, self.key) got = application.get_response('/foo.com/salmon', method='POST', body=slap) self.assertEqual(400, got.status_int)
def test_webfinger_handler_custom_username(self, mock_get): self.html = """ <body class="h-card"> <a class="u-url" rel="me" href="/about-me"> <img class="u-photo" src="/me.jpg" /> Mrs. ☕ Foo </a> <a class="u-url" href="acct:[email protected]"></a> <a class="u-url" href="acct:[email protected]"></a> </body> """ self.expected_webfinger['subject'] = "acct:[email protected]" self.expected_webfinger['aliases'] = [u'https://foo.com/about-me', u'acct:[email protected]', u'acct:[email protected]', u'https://foo.com/'] mock_get.return_value = requests_response(self.html, url='https://foo.com/') for resource in ('*****@*****.**', 'acct:[email protected]', 'foo.com', 'http://foo.com/', 'https://foo.com/'): url = '/.well-known/webfinger?%s' % urllib.parse.urlencode( {'resource': resource}) got = application.get_response(url, headers={'Accept': 'application/json'}) body = got.body.decode() self.assertEqual(200, got.status_int, body) self.assertEqual('application/json; charset=utf-8', got.headers['Content-Type']) self.assertEqual(self.expected_webfinger, json_loads(body))
def test_host_meta_handler_xrds(self): got = application.get_response('/.well-known/host-meta.xrds') self.assertEqual(200, got.status_int) self.assertEqual('application/xrds+xml; charset=utf-8', got.headers['Content-Type']) body = got.body.decode() self.assertTrue(body.startswith('<XRDS'), body)
def test_activitypub_create_default_url_to_wm_source( self, mock_get, mock_post): """Source post has no u-url. AS2 id should default to webmention source.""" missing_url = requests_response("""\ <html> <body class="h-entry"> <a class="u-repost-of p-name" href="http://orig/post">reposted!</a> <a class="p-author h-card" href="http://orig">Ms. ☕ Baz</a> <a href="http://localhost/"></a> </body> </html> """, content_type=CONTENT_TYPE_HTML) mock_get.side_effect = [missing_url, self.orig_as2, self.actor] mock_post.return_value = requests_response('abc xyz', status=203) got = application.get_response('/webmention', method='POST', body=urlencode({ 'source': 'http://a/repost', 'target': 'https://fed.brid.gy/', }).encode()) self.assertEqual(203, got.status_int) args, kwargs = mock_post.call_args self.assertEqual(('https://foo.com/inbox', ), args) self.assert_equals(self.repost_as2, json_loads(kwargs['data']))
def test_activitypub_link_rel_alternate_as2(self, mock_get, mock_post): mock_get.side_effect = [ self.reply, self.not_fediverse, self.orig_html_as2, self.orig_as2, self.actor ] mock_post.return_value = requests_response('abc xyz') got = application.get_response('/webmention', method='POST', body=urlencode({ 'source': 'http://a/reply', 'target': 'https://fed.brid.gy/', }).encode()) self.assertEqual(200, got.status_int) mock_get.assert_has_calls(( self.req('http://a/reply'), self.req('http://not/fediverse', headers=CONNEG_HEADERS_AS2_HTML), self.req('http://orig/post', headers=CONNEG_HEADERS_AS2_HTML), self.req('http://orig/as2', headers=CONNEG_HEADERS_AS2), self.req('http://orig/author', headers=CONNEG_HEADERS_AS2_HTML), )) args, kwargs = mock_post.call_args self.assertEqual(('https://foo.com/inbox', ), args) self.assertEqual(self.as2_create, json_loads(kwargs['data']))
def test_activitypub_follow(self, mock_get, mock_post): mock_get.side_effect = [self.follow, self.actor] mock_post.return_value = requests_response('abc xyz') got = application.get_response('/webmention', method='POST', body=urlencode({ 'source': 'http://a/follow', 'target': 'https://fed.brid.gy/', }).encode()) self.assertEqual(200, got.status_int) mock_get.assert_has_calls(( self.req('http://a/follow'), self.req('http://followee/', headers=CONNEG_HEADERS_AS2_HTML), )) args, kwargs = mock_post.call_args self.assertEqual(('https://foo.com/inbox', ), args) self.assertEqual(self.follow_as2, json_loads(kwargs['data'])) headers = kwargs['headers'] self.assertEqual(CONTENT_TYPE_AS2, headers['Content-Type']) rsa_key = kwargs['auth'].header_signer._rsa._key self.assertEqual(self.key.private_pem(), rsa_key.exportKey()) resp = Response.get_by_id('http://a/follow http://followee/') self.assertEqual('out', resp.direction) self.assertEqual('activitypub', resp.protocol) self.assertEqual('complete', resp.status) self.assertEqual(self.follow_mf2, json_loads(resp.source_mf2))
def test_salmon_relative_atom_href_with_base(self, mock_get, mock_post): orig_base = requests_response( """\ <html> <meta> <base href='/base/'> <link href='atom/1' rel='alternate' type='application/atom+xml'> </meta> </html>""", 'http://orig/url') mock_get.side_effect = [ self.reply, self.not_fediverse, orig_base, self.orig_atom ] got = application.get_response('/webmention', method='POST', body=urlencode({ 'source': 'http://a/reply', 'target': 'http://orig/post', }).encode()) self.assertEqual(200, got.status_int) mock_get.assert_any_call('http://orig/base/atom/1', headers=HEADERS, stream=True, timeout=util.HTTP_TIMEOUT) data = self.verify_salmon(mock_post)
def test_endpoint_param(self, mock_get): mock_get.return_value = self.resp got = application.get_response( '/wm/http://url?endpoint=https://end/point') self.assertEqual(200, got.status_int) self.assertEqual('<https://end/point>; rel="webmention"', got.headers['Link'])
def test_inbox_undo_follow_doesnt_exist(self, mock_head, mock_get, mock_post): mock_head.return_value = requests_response(url='https://realize.be/') got = application.get_response( '/foo.com/inbox', method='POST', body=json_dumps(UNDO_FOLLOW_WRAPPED).encode()) self.assertEqual(200, got.status_int)
def test_target_fetch_has_no_content_type(self, mock_get, mock_post): mock_get.side_effect = (requests_response(self.reply_html), requests_response(self.reply_html, content_type='None')) got = application.get_response('/webmention', method='POST', body=urlencode({ 'source': 'http://a/post' }).encode()) self.assertEqual(502, got.status_int)
def test_inbox_follow_accept(self, mock_head, mock_get, mock_post): mock_head.return_value = requests_response(url='https://realize.be/') mock_get.side_effect = [ # source actor requests_response(FOLLOW_WITH_ACTOR['actor'], content_type=common.CONTENT_TYPE_AS2), # target post webmention discovery requests_response( '<html><head><link rel="webmention" href="/webmention"></html>' ), ] mock_post.return_value = requests_response() got = application.get_response( '/foo.com/inbox', method='POST', body=json_dumps(FOLLOW_WRAPPED).encode()) self.assertEqual(200, got.status_int) as2_headers = copy.deepcopy(common.HEADERS) as2_headers.update(common.CONNEG_HEADERS_AS2_HTML) mock_get.assert_has_calls((call(FOLLOW['actor'], headers=as2_headers, stream=True, timeout=15), )) # check AP Accept self.assertEqual(2, len(mock_post.call_args_list)) args, kwargs = mock_post.call_args_list[0] self.assertEqual(('http://follower/inbox', ), args) self.assertEqual(ACCEPT, kwargs['json']) # check webmention args, kwargs = mock_post.call_args_list[1] self.assertEqual(('https://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/', }, kwargs['data']) resp = Response.get_by_id( 'https://mastodon.social/6d1a https://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'])) self.assertEqual('active', follower.status) self.assertEqual(FOLLOW_WRAPPED_WITH_ACTOR, json_loads(follower.last_follow))
def test_user_handler(self, mock_get): mock_get.return_value = requests_response(self.html, url = 'https://foo.com/') got = application.get_response('/acct:foo.com', headers={'Accept': 'application/json'}) self.assertEqual(200, got.status_int) self.assertEqual('application/json; charset=utf-8', got.headers['Content-Type']) mock_get.assert_called_once_with('http://foo.com/', headers=common.HEADERS, stream=True, timeout=util.HTTP_TIMEOUT) self.assertEqual(self.expected_webfinger, json_loads(got.body.decode())) # check that magic key is persistent again = json_loads(application.get_response( '/acct:foo.com', headers={'Accept': 'application/json'}).body.decode()) self.assertEqual(self.key.href(), again['magic_keys'][0]['value']) links = {l['rel']: l['href'] for l in again['links']} self.assertEqual(self.key.href(), links['magic-public-key'])
def test_inbox_undo_follow_inactive(self, mock_head, mock_get, mock_post): mock_head.return_value = requests_response(url='https://realize.be/') Follower(id=Follower._id('realize.be', 'https://mastodon.social/users/swentel'), status='inactive').put() got = application.get_response( '/foo.com/inbox', method='POST', body=json_dumps(UNDO_FOLLOW_WRAPPED).encode()) self.assertEqual(200, got.status_int)
def test_get(self, mock_get): self.resp.status_code = 202 mock_get.return_value = self.resp got = application.get_response('/wm/http://url') self.assertEqual(202, got.status_int) self.assertEqual(self.resp._content, got.body) self.assertEqual(['bar'], got.headers.getall('Foo')) self.assertEqual( ['first', '<http://localhost/webmention>; rel="webmention"'], got.headers.getall('Link'))
def test_target_fetch_fails(self, mock_get, mock_post): mock_get.side_effect = (requests_response( self.reply_html.replace('http://orig/post', 'bad'), content_type=CONTENT_TYPE_HTML), requests.Timeout('foo bar')) got = application.get_response('/webmention', method='POST', body=urlencode({ 'source': 'http://a/post' }).encode()) self.assertEqual(502, got.status_int)
def test_inbox_undo_follow(self, mock_head, mock_get, mock_post): mock_head.return_value = requests_response(url='https://realize.be/') Follower(id=Follower._id('realize.be', FOLLOW['actor'])).put() got = application.get_response( '/foo.com/inbox', method='POST', body=json_dumps(UNDO_FOLLOW_WRAPPED).encode()) self.assertEqual(200, got.status_int) follower = Follower.get_by_id('realize.be %s' % FOLLOW['actor']) self.assertEqual('inactive', follower.status)
def test_user_handler_no_hcard(self, mock_get): mock_get.return_value = requests_response(""" <body> <div class="h-entry"> <p class="e-content">foo bar</p> </div> </body> """) got = application.get_response('/acct:foo.com') mock_get.assert_called_once_with('http://foo.com/', headers=common.HEADERS, stream=True, timeout=util.HTTP_TIMEOUT) self.assertEqual(400, got.status_int) self.assertIn('representative h-card', got.body.decode())
def test_webfinger_handler(self, mock_get): mock_get.return_value = requests_response(self.html, url='https://foo.com/') for resource in ('*****@*****.**', 'acct:[email protected]', '*****@*****.**', 'foo.com', 'http://foo.com/', 'https://foo.com/'): url = '/.well-known/webfinger?%s' % urllib.parse.urlencode( {'resource': resource}) got = application.get_response(url, headers={'Accept': 'application/json'}) body = got.body.decode() self.assertEqual(200, got.status_int, body) self.assertEqual('application/json; charset=utf-8', got.headers['Content-Type']) self.assertEqual(self.expected_webfinger, json_loads(body))
def test_inbox_delete_actor(self, mock_head, mock_get, mock_post): follower = Follower.get_or_create('realize.be', DELETE['actor']) Follower.get_or_create('snarfed.org', DELETE['actor']) # other unrelated follower other = Follower.get_or_create('realize.be', 'https://mas.to/users/other') self.assertEqual(3, Follower.query().count()) got = application.get_response('/realize.be/inbox', method='POST', body=json_dumps(DELETE).encode()) self.assertEqual(200, got.status_int) self.assertEqual([other], Follower.query().fetch())
def test_salmon_reply(self, mock_get, mock_post): mock_get.side_effect = [ self.reply, self.not_fediverse, self.orig_html_atom, self.orig_atom ] got = application.get_response('/webmention', method='POST', body=urlencode({ 'source': 'http://a/reply', 'target': 'http://orig/post', }).encode()) self.assertEqual(200, got.status_int) mock_get.assert_has_calls(( self.req('http://a/reply'), self.req('http://not/fediverse', headers=CONNEG_HEADERS_AS2_HTML), self.req('http://orig/post', headers=CONNEG_HEADERS_AS2_HTML), self.req('http://orig/atom'), )) data = self.verify_salmon(mock_post) parsed = feedparser.parse(data) entry = parsed.entries[0] self.assertEqual('http://a/reply', entry['id']) self.assertIn( { 'rel': 'alternate', 'href': 'http://a/reply', 'type': 'text/html', }, entry['links']) self.assertEqual( { 'type': 'text/html', 'href': 'http://orig/post', 'ref': 'tag:fed.brid.gy,2017-08-22:orig-post', }, entry['thr_in-reply-to']) self.assertEqual( """\ <a class="u-in-reply-to" href="http://not/fediverse"></a><br></br> <a class="u-in-reply-to" href="http://orig/post">foo ☕ bar</a><br></br> <a href="http://localhost/"></a>""", entry.content[0]['value']) resp = Response.get_by_id('http://a/reply http://orig/post') self.assertEqual('out', resp.direction) self.assertEqual('ostatus', resp.protocol) self.assertEqual('complete', resp.status) self.assertEqual(self.reply_mf2, json_loads(resp.source_mf2))
def test_rsvp_not_supported(self, *mocks): slap = magicsigs.magic_envelope( """\ <?xml version='1.0' encoding='UTF-8'?> <entry xmlns='http://www.w3.org/2005/Atom' xmlns:activity='http://activitystrea.ms/spec/1.0/'> <uri>https://my/rsvp</uri> <activity:verb>http://activitystrea.ms/schema/1.0/rsvp</activity:verb> <activity:object>http://orig/event</activity:object> </entry>""", common.CONTENT_TYPE_ATOM, self.key) got = application.get_response('/foo.com/salmon', method='POST', body=slap) self.assertEqual(501, got.status_int)
def test_user_handler_with_push_header(self, mock_get): mock_get.return_value = requests_response( self.html, url = 'https://foo.com/', headers={ 'Link': 'badly formatted, ' "<xyz>; rel='foo'," '<http://a.custom.hub/>; rel="hub"', }) got = application.get_response('/acct:foo.com', headers={'Accept': 'application/json'}) self.assertEqual(200, got.status_int) self.assertIn({ 'rel': 'hub', 'href': 'http://a.custom.hub/', }, json_loads(got.body.decode())['links'])
def test_no_backlink(self, mock_get, mock_post): mock_get.return_value = requests_response( self.reply_html.replace('<a href="http://localhost/"></a>', ''), content_type=CONTENT_TYPE_HTML) got = application.get_response('/webmention', method='POST', body=urlencode({ 'source': 'http://a/post', 'target': 'https://fed.brid.gy/', }).encode()) self.assertEqual(400, got.status_int) mock_get.assert_has_calls((self.req('http://a/post'), ))
def test_inbox_unsupported_type(self, *_): got = application.get_response( '/foo.com/inbox', method='POST', body=json_dumps({ '@context': ['https://www.w3.org/ns/activitystreams'], 'id': 'https://xoxo.zone/users/aaronpk#follows/40', 'type': 'Block', 'actor': 'https://xoxo.zone/users/aaronpk', 'object': 'http://snarfed.org/', }).encode()) self.assertEqual(501, got.status_int)
def send_slap(self, mock_urlopen, mock_head, mock_get, mock_post, atom_slap): # salmon magic key discovery. first host-meta, then webfinger mock_urlopen.side_effect = [ UrlopenResult( 200, """\ <?xml version='1.0' encoding='UTF-8'?> <XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'> <Link rel='lrdd' type='application/xrd+xml' template='http://webfinger/{uri}' /> </XRD>"""), UrlopenResult( 200, """\ <?xml version='1.0' encoding='UTF-8'?> <XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'> <Subject>[email protected]</Subject> <Link rel='magic-public-key' href='%s' /> </XRD>""" % self.key.href()), ] # webmention discovery mock_head.return_value = requests_response(url='http://orig/post') mock_get.return_value = requests_response( '<html><head><link rel="webmention" href="/webmention"></html>') # webmention post mock_post.return_value = requests_response() slap = magicsigs.magic_envelope(atom_slap, common.CONTENT_TYPE_ATOM, self.key) got = application.get_response('/[email protected]/salmon', method='POST', body=slap) self.assertEqual(200, got.status_int) # check salmon magic key discovery mock_urlopen.assert_has_calls(( mock.call('http://fedsoc.net/.well-known/host-meta'), mock.call('http://webfinger/[email protected]'), )) # check webmention discovery self.expected_headers = copy.deepcopy(common.HEADERS) self.expected_headers['Accept'] = '*/*' mock_get.assert_called_once_with('http://orig/post', headers=common.HEADERS, verify=False)
def test_activitypub_create_author_only_url(self, mock_get, mock_post): """Mf2 author property is just a URL. We should run full authorship. https://indieweb.org/authorship """ repost = requests_response("""\ <html> <body class="h-entry"> <a class="u-repost-of p-name" href="http://orig/post">reposted!</a> <a class="u-author" href="http://orig"></a> <a href="http://localhost/"></a> </body> </html> """, content_type=CONTENT_TYPE_HTML) author = requests_response("""\ <html> <body class="h-card"> <a class="p-name u-url" rel="me" href="http://orig">Ms. ☕ Baz</a> <img class="u-photo" src="/pic" /> </body> </html> """, url='http://orig', content_type=CONTENT_TYPE_HTML) mock_get.side_effect = [repost, author, self.orig_as2, self.actor] mock_post.return_value = requests_response('abc xyz', status=201) got = application.get_response('/webmention', method='POST', body=urlencode({ 'source': 'http://a/repost', 'target': 'https://fed.brid.gy/', }).encode()) self.assertEqual(201, got.status_int) args, kwargs = mock_post.call_args self.assertEqual(('https://foo.com/inbox', ), args) repost_as2 = copy.deepcopy(self.repost_as2) repost_as2['actor']['image'] = repost_as2['actor']['icon'] = \ {'type': 'Image', 'url': 'http://orig/pic'}, self.assert_equals(repost_as2, json_loads(kwargs['data']))