always sign requests with current user's key, stop defaulting to snarfed.org

for #403
pull/413/head
Ryan Barrett 2023-02-06 19:23:25 -08:00
rodzic 717b068193
commit 4b37674624
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
7 zmienionych plików z 137 dodań i 110 usunięć

Wyświetl plik

@ -71,6 +71,7 @@ DOMAIN_BLOCKLIST = frozenset((
'twitter.com',
) + DOMAINS)
# currently unused. TODO: remove?
_DEFAULT_SIGNATURE_USER = None
CACHE_TIME = timedelta(seconds=60)
@ -87,6 +88,7 @@ def host_url(path_query=None):
return urllib.parse.urljoin(base, path_query)
# currently unused. TODO: remove?
def default_signature_user():
global _DEFAULT_SIGNATURE_USER
if _DEFAULT_SIGNATURE_USER is None:
@ -94,38 +96,35 @@ def default_signature_user():
return _DEFAULT_SIGNATURE_USER
def signed_get(url, **kwargs):
return signed_request(util.requests_get, url, **kwargs)
def signed_get(url, user, **kwargs):
return signed_request(util.requests_get, url, user, **kwargs)
def signed_post(url, **kwargs):
return signed_request(util.requests_post, url, **kwargs)
def signed_post(url, user, **kwargs):
return signed_request(util.requests_post, url, user, **kwargs)
def signed_request(fn, url, data=None, log_data=True, user=None, headers=None, **kwargs):
def signed_request(fn, url, user, data=None, log_data=True, headers=None, **kwargs):
"""Wraps requests.* and adds HTTP Signature.
Args:
fn: :func:`util.requests_get` or :func:`util.requests_get`
url: str
user: :class:`User` to sign request with
data: optional AS2 object
log_data: boolean, whether to log full data object
user: optional :class:`User` to sign request with
kwargs: passed through to requests
Returns: :class:`requests.Response`
"""
assert user
if headers is None:
headers = {}
# prepare HTTP Signature and headers
if not user:
user = default_signature_user()
if data:
if log_data:
logging.info(f'Sending AS2 object: {json_dumps(data, indent=2)}')
data = kwargs['data'] = json_dumps(data).encode()
data = json_dumps(data).encode()
headers = copy.deepcopy(headers)
headers.update({
@ -154,13 +153,15 @@ def signed_request(fn, url, data=None, log_data=True, user=None, headers=None, *
# make HTTP request
kwargs.setdefault('gateway', True)
resp = fn(url, auth=auth, headers=headers, allow_redirects=False, **kwargs)
resp = fn(url, data=data, auth=auth, headers=headers, allow_redirects=False,
**kwargs)
logger.info(f'Got {resp.status_code} headers: {resp.headers}')
# handle GET redirects manually so that we generate a new HTTP signature
if resp.is_redirect and fn == util.requests_get:
return signed_request(fn, resp.headers['Location'], data=data, user=user,
headers=headers, **kwargs)
headers=headers, log_data=log_data, **kwargs)
type = content_type(resp)
if (type and type != 'text/html' and
(type.startswith('text/') or type.endswith('+json') or type.endswith('/json'))):
@ -199,6 +200,8 @@ def get_as2(url, user=None):
If we raise a werkzeug HTTPException, it will have an additional
requests_response attribute with the last requests.Response we received.
"""
assert user
def _error(resp):
msg = f"Couldn't fetch {url} as ActivityStreams 2"
logger.warning(msg)
@ -217,7 +220,7 @@ def get_as2(url, user=None):
_error(resp)
resp = signed_get(urllib.parse.urljoin(resp.url, obj['href']),
headers=as2.CONNEG_HEADERS)
user=user, headers=as2.CONNEG_HEADERS)
if content_type(resp) in (as2.CONTENT_TYPE, CONTENT_TYPE_LD_PLAIN):
return resp

Wyświetl plik

@ -147,7 +147,7 @@ class FollowCallback(indieauth.Callback):
flash(f"Couldn't find ActivityPub profile link for {addr}")
return redirect(f'/user/{domain}/following')
resp = common.get_as2(as2_url)
resp = common.get_as2(as2_url, user=user)
followee = resp.json()
id = followee.get('id')
inbox = followee.get('inbox')
@ -165,7 +165,7 @@ class FollowCallback(indieauth.Callback):
'actor': common.host_url(domain),
'to': [as2.PUBLIC_AUDIENCE],
}
common.signed_post(inbox, data=follow_as2)
common.signed_post(inbox, user=user, data=follow_as2)
follow_json = json_dumps(follow_as2, sort_keys=True)
Follower.get_or_create(dest=id, src=domain, status='active',
@ -218,7 +218,7 @@ class UnfollowCallback(indieauth.Callback):
if isinstance(followee, str):
# fetch as AS2 to get full followee with inbox
followee_id = followee
resp = common.get_as2(followee_id)
resp = common.get_as2(followee_id, user=user)
followee = resp.json()
inbox = followee.get('inbox')
@ -235,7 +235,7 @@ class UnfollowCallback(indieauth.Callback):
'actor': common.host_url(domain),
'object': last_follow,
}
common.signed_post(inbox, data=unfollow_as2)
common.signed_post(inbox, user=user, data=unfollow_as2)
follower.status = 'inactive'
follower.put()

Wyświetl plik

@ -3,7 +3,7 @@
from unittest import mock
from granary import as2
from oauth_dropins.webutil import util
from oauth_dropins.webutil import appengine_config, util
from oauth_dropins.webutil.testutil import requests_response
import requests
from werkzeug.exceptions import BadGateway
@ -31,6 +31,12 @@ NOT_ACCEPTABLE = requests_response(status=406)
class CommonTest(testutil.TestCase):
@classmethod
def setUpClass(cls):
with appengine_config.ndb_client.context():
# do this in setUpClass since generating RSA keys is slow
cls.user = User.get_or_create('site')
def setUp(self):
super().setUp()
self.app_context = app.test_request_context('/')
@ -42,7 +48,7 @@ class CommonTest(testutil.TestCase):
@mock.patch('requests.get', return_value=AS2)
def test_get_as2_direct(self, mock_get):
resp = common.get_as2('http://orig')
resp = common.get_as2('http://orig', user=self.user)
self.assertEqual(AS2, resp)
mock_get.assert_has_calls((
self.as2_req('http://orig'),
@ -50,7 +56,7 @@ class CommonTest(testutil.TestCase):
@mock.patch('requests.get', side_effect=[HTML_WITH_AS2, AS2])
def test_get_as2_via_html(self, mock_get):
resp = common.get_as2('http://orig')
resp = common.get_as2('http://orig', user=self.user)
self.assertEqual(AS2, resp)
mock_get.assert_has_calls((
self.as2_req('http://orig'),
@ -60,17 +66,17 @@ class CommonTest(testutil.TestCase):
@mock.patch('requests.get', return_value=HTML)
def test_get_as2_only_html(self, mock_get):
with self.assertRaises(BadGateway):
resp = common.get_as2('http://orig')
resp = common.get_as2('http://orig', user=self.user)
@mock.patch('requests.get', return_value=NOT_ACCEPTABLE)
def test_get_as2_not_acceptable(self, mock_get):
with self.assertRaises(BadGateway):
resp = common.get_as2('http://orig')
resp = common.get_as2('http://orig', user=self.user)
@mock.patch('requests.get', side_effect=requests.exceptions.SSLError)
def test_get_ssl_error(self, mock_get):
with self.assertRaises(BadGateway):
resp = common.get_as2('http://orig')
resp = common.get_as2('http://orig', user=self.user)
def test_redirect_wrap_empty(self):
self.assertIsNone(common.redirect_wrap(None))
@ -97,7 +103,7 @@ class CommonTest(testutil.TestCase):
}, common.postprocess_as2({
'id': 'xyz',
'inReplyTo': ['foo', 'bar'],
}, user=User(id='foo.com')))
}, user=User(id='site')))
def test_postprocess_as2_multiple_url(self):
with app.test_request_context('/'):
@ -108,7 +114,7 @@ class CommonTest(testutil.TestCase):
}, common.postprocess_as2({
'id': 'xyz',
'url': ['foo', 'bar'],
}, user=User(id='foo.com')))
}, user=User(id='site')))
def test_postprocess_as2_multiple_image(self):
with app.test_request_context('/'):
@ -120,30 +126,30 @@ class CommonTest(testutil.TestCase):
}, common.postprocess_as2({
'id': 'xyz',
'image': [{'url': 'http://r/foo'}, {'url': 'http://r/bar'}],
}, user=User(id='foo.com')))
}, user=User(id='site')))
def test_postprocess_as2_actor_attributedTo(self):
with app.test_request_context('/'):
self.assert_equals({
'actor': {
'id': 'baj',
'preferredUsername': 'foo.com',
'url': 'http://localhost/r/https://foo.com/',
'preferredUsername': 'site',
'url': 'http://localhost/r/https://site/',
},
'attributedTo': [{
'id': 'bar',
'preferredUsername': 'foo.com',
'url': 'http://localhost/r/https://foo.com/',
'preferredUsername': 'site',
'url': 'http://localhost/r/https://site/',
}, {
'id': 'baz',
'preferredUsername': 'foo.com',
'url': 'http://localhost/r/https://foo.com/',
'preferredUsername': 'site',
'url': 'http://localhost/r/https://site/',
}],
'to': [as2.PUBLIC_AUDIENCE],
}, common.postprocess_as2({
'attributedTo': [{'id': 'bar'}, {'id': 'baz'}],
'actor': {'id': 'baj'},
}, user=User(id='foo.com')))
}, user=User(id='site')))
def test_postprocess_as2_note(self):
with app.test_request_context('/'):
@ -152,9 +158,9 @@ class CommonTest(testutil.TestCase):
'id': 'http://localhost/r/xyz#bridgy-fed-create',
'type': 'Create',
'actor': {
'id': 'http://localhost/foo.com',
'url': 'http://localhost/r/https://foo.com/',
'preferredUsername': 'foo.com'
'id': 'http://localhost/site',
'url': 'http://localhost/r/https://site/',
'preferredUsername': 'site'
},
'object': {
'id': 'http://localhost/r/xyz',
@ -164,7 +170,7 @@ class CommonTest(testutil.TestCase):
}, common.postprocess_as2({
'id': 'xyz',
'type': 'Note',
}, user=User(id='foo.com')))
}, user=User(id='site')))
def test_host_url(self):
with app.test_request_context():
@ -187,7 +193,7 @@ class CommonTest(testutil.TestCase):
allow_redirects=False),
requests_response(status=200, allow_redirects=False),
]
resp = common.signed_get('https://first')
resp = common.signed_get('https://first', user=self.user)
first = mock_get.call_args_list[0][1]
second = mock_get.call_args_list[1][1]
@ -202,6 +208,6 @@ class CommonTest(testutil.TestCase):
requests_response(status=302, redirected_url='http://second',
allow_redirects=False),
]
resp = common.signed_post('https://first')
resp = common.signed_post('https://first', user=self.user)
mock_post.assert_called_once()
self.assertEqual(302, resp.status_code)

Wyświetl plik

@ -37,18 +37,18 @@ FOLLOWEE = {
FOLLOW_ADDRESS = {
'@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Follow',
'id': f'http://localhost/user/snarfed.org/following#2022-01-02T03:04:05-@foo@bar',
'actor': 'http://localhost/snarfed.org',
'id': f'http://localhost/user/alice.com/following#2022-01-02T03:04:05-@foo@bar',
'actor': 'http://localhost/alice.com',
'object': FOLLOWEE,
'to': [as2.PUBLIC_AUDIENCE],
}
FOLLOW_URL = copy.deepcopy(FOLLOW_ADDRESS)
FOLLOW_URL['id'] = f'http://localhost/user/snarfed.org/following#2022-01-02T03:04:05-https://bar/actor'
FOLLOW_URL['id'] = f'http://localhost/user/alice.com/following#2022-01-02T03:04:05-https://bar/actor'
UNDO_FOLLOW = {
'@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Undo',
'id': f'http://localhost/user/snarfed.org/following#undo-2022-01-02T03:04:05-https://bar/id',
'actor': 'http://localhost/snarfed.org',
'id': f'http://localhost/user/alice.com/following#undo-2022-01-02T03:04:05-https://bar/id',
'actor': 'http://localhost/alice.com',
'object': FOLLOW_ADDRESS,
}
@ -126,7 +126,7 @@ class FollowTest(testutil.TestCase):
mock_get.return_value = requests_response('') # IndieAuth endpoint discovery
resp = self.client.post('/follow/start', data={
'me': 'https://snarfed.org',
'me': 'https://alice.com',
'address': '@foo@bar',
})
self.assertEqual(302, resp.status_code)
@ -135,7 +135,7 @@ class FollowTest(testutil.TestCase):
def test_callback_address(self, mock_get, mock_post):
mock_get.side_effect = (
# oauth-dropins indieauth https://snarfed.org fetch for user json
# oauth-dropins indieauth https://alice.com fetch for user json
requests_response(''),
WEBFINGER,
self.as2_resp(FOLLOWEE),
@ -153,22 +153,22 @@ class FollowTest(testutil.TestCase):
self._test_callback('https://bar/actor', FOLLOW_URL, mock_get, mock_post)
def _test_callback(self, input, expected_follow, mock_get, mock_post):
User.get_or_create('snarfed.org')
User.get_or_create('alice.com')
mock_post.side_effect = (
requests_response('me=https://snarfed.org'),
requests_response('me=https://alice.com'),
requests_response('OK'), # AP Follow to inbox
)
state = util.encode_oauth_state({
'endpoint': 'http://auth/endpoint',
'me': 'https://snarfed.org',
'me': 'https://alice.com',
'state': input,
})
with self.client:
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
self.assertEqual(302, resp.status_code)
self.assertEqual('/user/snarfed.org/following',resp.headers['Location'])
self.assertEqual('/user/alice.com/following',resp.headers['Location'])
self.assertEqual([f'Followed <a href="https://bar/url">{input}</a>.'],
get_flashed_messages())
@ -180,25 +180,30 @@ class FollowTest(testutil.TestCase):
self.assertEqual(('http://bar/inbox',), inbox_args)
self.assert_equals(expected_follow, json_loads(inbox_kwargs['data']))
# check that we signed with the follower's key
sig_template = inbox_kwargs['auth'].header_signer.signature_template
self.assertTrue(sig_template.startswith('keyId="http://localhost/alice.com"'),
sig_template)
followers = Follower.query().fetch()
self.assert_entities_equal(
Follower(id='https://bar/id snarfed.org',
Follower(id='https://bar/id alice.com',
last_follow=json_dumps(expected_follow, sort_keys=True),
src='snarfed.org', dest='https://bar/id', status='active'),
src='alice.com', dest='https://bar/id', status='active'),
followers,
ignore=['created', 'updated'])
id = f'http://localhost/user/snarfed.org/following#2022-01-02T03:04:05-{input}'
self.assert_object(id, domains=['snarfed.org'], status='complete',
id = f'http://localhost/user/alice.com/following#2022-01-02T03:04:05-{input}'
self.assert_object(id, domains=['alice.com'], status='complete',
labels=['user', 'activity'], source_protocol='ui',
as2=expected_follow, as1=as2.to_as1(expected_follow))
def test_callback_missing_user(self, mock_get, mock_post):
mock_post.return_value = requests_response('me=https://snarfed.org')
mock_post.return_value = requests_response('me=https://alice.com')
state = util.encode_oauth_state({
'endpoint': 'http://auth/endpoint',
'me': 'https://snarfed.org',
'me': 'https://alice.com',
'state': '@foo@bar',
})
with self.client:
@ -206,46 +211,46 @@ class FollowTest(testutil.TestCase):
self.assertEqual(400, resp.status_code)
def test_callback_user_use_instead(self, mock_get, mock_post):
user = User.get_or_create('www.snarfed.org')
User.get_or_create('snarfed.org', use_instead=user.key)
user = User.get_or_create('www.alice.com')
User.get_or_create('alice.com', use_instead=user.key)
mock_get.side_effect = (
requests_response(''),
self.as2_resp(FOLLOWEE),
)
mock_post.side_effect = (
requests_response('me=https://snarfed.org'),
requests_response('me=https://alice.com'),
requests_response('OK'), # AP Follow to inbox
)
state = util.encode_oauth_state({
'endpoint': 'http://auth/endpoint',
'me': 'https://snarfed.org',
'me': 'https://alice.com',
'state': 'https://bar/actor',
})
with self.client:
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
self.assertEqual(302, resp.status_code)
self.assertEqual('/user/www.snarfed.org/following', resp.headers['Location'])
self.assertEqual('/user/www.alice.com/following', resp.headers['Location'])
id = 'http://localhost/user/www.snarfed.org/following#2022-01-02T03:04:05-https://bar/actor'
id = 'http://localhost/user/www.alice.com/following#2022-01-02T03:04:05-https://bar/actor'
expected_follow = {
'@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Follow',
'id': id,
'actor': 'http://localhost/www.snarfed.org',
'actor': 'http://localhost/www.alice.com',
'object': FOLLOWEE,
'to': [as2.PUBLIC_AUDIENCE],
}
followers = Follower.query().fetch()
self.assert_entities_equal(
Follower(id='https://bar/id www.snarfed.org',
Follower(id='https://bar/id www.alice.com',
last_follow=json_dumps(expected_follow, sort_keys=True),
src='www.snarfed.org', dest='https://bar/id', status='active'),
src='www.alice.com', dest='https://bar/id', status='active'),
followers,
ignore=['created', 'updated'])
self.assert_object(id, domains=['www.snarfed.org'], status='complete',
self.assert_object(id, domains=['www.alice.com'], status='complete',
labels=['user', 'activity'], source_protocol='ui',
as2=expected_follow, as1=as2.to_as1(expected_follow))
@ -257,15 +262,15 @@ class UnfollowTest(testutil.TestCase):
def setUp(self):
super().setUp()
self.follower = Follower(
id='https://bar/id snarfed.org', last_follow=json_dumps(FOLLOW_ADDRESS),
src='snarfed.org', dest='https://bar/id', status='active',
id='https://bar/id alice.com', last_follow=json_dumps(FOLLOW_ADDRESS),
src='alice.com', dest='https://bar/id', status='active',
).put()
def test_start(self, mock_get, _):
mock_get.return_value = requests_response('') # IndieAuth endpoint discovery
resp = self.client.post('/unfollow/start', data={
'me': 'https://snarfed.org',
'me': 'https://alice.com',
'key': self.follower.id(),
})
self.assertEqual(302, resp.status_code)
@ -284,7 +289,7 @@ class UnfollowTest(testutil.TestCase):
})
follower.put()
# oauth-dropins indieauth https://snarfed.org fetch for user json
# oauth-dropins indieauth https://alice.com fetch for user json
mock_get.side_effect = (
requests_response(''),
self.as2_resp(FOLLOWEE), # fetch to discover inbox
@ -296,21 +301,21 @@ class UnfollowTest(testutil.TestCase):
self._test_callback(undo, mock_get, mock_post)
def _test_callback(self, expected_undo, mock_get, mock_post):
User.get_or_create('snarfed.org')
User.get_or_create('alice.com')
mock_post.side_effect = (
requests_response('me=https://snarfed.org'),
requests_response('me=https://alice.com'),
requests_response('OK'), # AP Undo Follow to inbox
)
state = util.encode_oauth_state({
'endpoint': 'http://auth/endpoint',
'me': 'https://snarfed.org',
'me': 'https://alice.com',
'state': self.follower.id(),
})
with self.client:
resp = self.client.get(f'/unfollow/callback?code=my_code&state={state}')
self.assertEqual(302, resp.status_code)
self.assertEqual('/user/snarfed.org/following',resp.headers['Location'])
self.assertEqual('/user/alice.com/following', resp.headers['Location'])
self.assertEqual([f'Unfollowed <a href="https://bar/url">bar/url</a>.'],
get_flashed_messages())
@ -318,22 +323,27 @@ class UnfollowTest(testutil.TestCase):
self.assertEqual(('http://bar/inbox',), inbox_args)
self.assert_equals(expected_undo, json_loads(inbox_kwargs['data']))
follower = Follower.get_by_id('https://bar/id snarfed.org')
# check that we signed with the follower's key
sig_template = inbox_kwargs['auth'].header_signer.signature_template
self.assertTrue(sig_template.startswith('keyId="http://localhost/alice.com"'),
sig_template)
follower = Follower.get_by_id('https://bar/id alice.com')
self.assertEqual('inactive', follower.status)
self.assert_object(
'http://localhost/user/snarfed.org/following#undo-2022-01-02T03:04:05-https://bar/id',
domains=['snarfed.org'], status='complete',
'http://localhost/user/alice.com/following#undo-2022-01-02T03:04:05-https://bar/id',
domains=['alice.com'], status='complete',
source_protocol='ui', labels=['user', 'activity'],
as2=expected_undo, as1=as2.to_as1(expected_undo))
def test_callback_user_use_instead(self, mock_get, mock_post):
user = User.get_or_create('www.snarfed.org')
User.get_or_create('snarfed.org', use_instead=user.key)
user = User.get_or_create('www.alice.com')
User.get_or_create('alice.com', use_instead=user.key)
self.follower = Follower(
id='https://bar/id www.snarfed.org', last_follow=json_dumps(FOLLOW_ADDRESS),
src='www.snarfed.org', dest='https://bar/id', status='active',
id='https://bar/id www.alice.com', last_follow=json_dumps(FOLLOW_ADDRESS),
src='www.alice.com', dest='https://bar/id', status='active',
).put()
mock_get.side_effect = (
@ -341,26 +351,26 @@ class UnfollowTest(testutil.TestCase):
self.as2_resp(FOLLOWEE),
)
mock_post.side_effect = (
requests_response('me=https://snarfed.org'),
requests_response('me=https://alice.com'),
requests_response('OK'), # AP Undo Follow to inbox
)
state = util.encode_oauth_state({
'endpoint': 'http://auth/endpoint',
'me': 'https://snarfed.org',
'me': 'https://alice.com',
'state': self.follower.id(),
})
with self.client:
resp = self.client.get(f'/unfollow/callback?code=my_code&state={state}')
self.assertEqual(302, resp.status_code)
self.assertEqual('/user/www.snarfed.org/following',resp.headers['Location'])
self.assertEqual('/user/www.alice.com/following', resp.headers['Location'])
id = 'http://localhost/user/www.snarfed.org/following#undo-2022-01-02T03:04:05-https://bar/id'
id = 'http://localhost/user/www.alice.com/following#undo-2022-01-02T03:04:05-https://bar/id'
expected_undo = {
'@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Undo',
'id': id,
'actor': 'http://localhost/www.snarfed.org',
'actor': 'http://localhost/www.alice.com',
'object': FOLLOW_ADDRESS,
}
@ -368,9 +378,9 @@ class UnfollowTest(testutil.TestCase):
self.assertEqual(('http://bar/inbox',), inbox_args)
self.assert_equals(expected_undo, json_loads(inbox_kwargs['data']))
follower = Follower.get_by_id('https://bar/id www.snarfed.org')
follower = Follower.get_by_id('https://bar/id www.alice.com')
self.assertEqual('inactive', follower.status)
self.assert_object(id, domains=['www.snarfed.org'], status='complete',
self.assert_object(id, domains=['www.alice.com'], status='complete',
source_protocol='ui', labels=['user', 'activity'],
as2=expected_undo, as1=as2.to_as1(expected_undo))

Wyświetl plik

@ -7,7 +7,7 @@ from urllib.parse import urlencode
import feedparser
from granary import as2, atom, microformats2
from httpsig.sign import HeaderSigner
from oauth_dropins.webutil import util
from oauth_dropins.webutil import appengine_config, util
from oauth_dropins.webutil.appengine_config import tasks_client
from oauth_dropins.webutil.appengine_info import APP_ID
from oauth_dropins.webutil.testutil import requests_response
@ -18,7 +18,6 @@ import activitypub
from common import (
CONNEG_HEADERS_AS2_HTML,
CONTENT_TYPE_HTML,
default_signature_user,
redirect_unwrap,
)
from models import Follower, Object, Target, User
@ -108,8 +107,8 @@ REPOST_AS2 = {
class WebmentionTest(testutil.TestCase):
def setUp(self):
super().setUp()
self.user = User.get_or_create('a')
self.user_orig = User.get_or_create('orig')
self.user_a = User.get_or_create('a')
self.orig_html_as2 = requests_response("""\
<html>
<meta>
@ -484,7 +483,7 @@ class WebmentionTest(testutil.TestCase):
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
rsa_key = kwargs['auth'].header_signer._rsa._key
self.assertEqual(self.user.private_pem(), rsa_key.exportKey())
self.assertEqual(self.user_a.private_pem(), rsa_key.exportKey())
self.assert_object('http://a/reply',
domains=['a'],
@ -603,13 +602,12 @@ class WebmentionTest(testutil.TestCase):
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
rsa_key = kwargs['auth'].header_signer._rsa._key
self.assertEqual(self.user.private_pem(), rsa_key.exportKey())
self.assertEqual(self.user_a.private_pem(), rsa_key.exportKey())
for args, kwargs in mock_get.call_args_list[1:]:
with self.subTest(url=args[0]):
rsa_key = kwargs['auth'].header_signer._rsa._key
self.assertEqual(default_signature_user().private_pem(),
rsa_key.exportKey())
self.assertEqual(self.user_a.private_pem(), rsa_key.exportKey())
self.assert_object('http://a/repost',
domains=['a'],
@ -909,7 +907,7 @@ class WebmentionTest(testutil.TestCase):
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
rsa_key = kwargs['auth'].header_signer._rsa._key
self.assertEqual(self.user.private_pem(), rsa_key.exportKey())
self.assertEqual(self.user_a.private_pem(), rsa_key.exportKey())
self.assert_object('http://a/follow',
domains=['a'],
@ -931,8 +929,8 @@ class WebmentionTest(testutil.TestCase):
self.assertEqual(self.follow_as2, json_loads(followers[0].last_follow))
def test_follow_no_actor(self, mock_get, mock_post):
self.user.actor_as2 = json_dumps(self.follow_as2['actor'])
self.user.put()
self.user_orig.actor_as2 = json_dumps(self.follow_as2['actor'])
self.user_orig.put()
html = self.follow_html.replace(
'<a class="p-author h-card" href="https://orig">Ms. ☕ Baz</a>', '')
@ -976,7 +974,7 @@ class WebmentionTest(testutil.TestCase):
self.assert_equals(as2.CONTENT_TYPE, headers['Content-Type'])
rsa_key = kwargs['auth'].header_signer._rsa._key
self.assert_equals(self.user.private_pem(), rsa_key.exportKey())
self.assert_equals(self.user_a.private_pem(), rsa_key.exportKey())
self.assert_object('http://a/follow#2',
domains=['a'],
@ -1033,7 +1031,7 @@ class WebmentionTest(testutil.TestCase):
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
rsa_key = kwargs['auth'].header_signer._rsa._key
self.assertEqual(self.user.private_pem(), rsa_key.exportKey())
self.assertEqual(self.user_a.private_pem(), rsa_key.exportKey())
self.assert_object('http://a/follow',
domains=['a'],
@ -1132,3 +1130,9 @@ class WebmentionTest(testutil.TestCase):
object_ids=['https://orig'],
labels=['user', 'activity'],
)
def test_no_user(self, mock_get, mock_post):
mock_get.side_effect = [requests_response(self.reply_html)]
got = self.client.post('/webmention', data={'source': 'https://no-user/post'})
self.assertEqual(400, got.status_code)
self.assertEqual(0, Object.query().count())

Wyświetl plik

@ -55,8 +55,9 @@ class TestCase(unittest.TestCase, testutil.Asserts):
**common.CONNEG_HEADERS_AS2_HTML,
**kwargs.pop('headers', {}),
}
return self.req(url, auth=ANY, headers=headers, allow_redirects=False,
**kwargs)
return self.req(url, data=None, auth=ANY, headers=headers,
allow_redirects=False, **kwargs)
def as2_resp(self, obj):
return requests_response(obj, content_type=as2.CONTENT_TYPE)

Wyświetl plik

@ -116,7 +116,10 @@ class Webmention(View):
))
logger.info(f'Converted webmention to AS1: {type_label}: {json_dumps(self.source_as1, indent=2)}')
self.user = User.get_or_create(self.source_domain)
self.user = User.get_by_id(self.source_domain)
if not self.user:
error(f'No user found for domain {self.source_domain}')
ret = self.try_activitypub()
return ret or 'No ActivityPub targets'
@ -210,8 +213,8 @@ class Webmention(View):
last_follow=json_dumps(self.source_as2))
try:
last = common.signed_post(inbox, data=self.source_as2,
log_data=log_data, user=self.user)
last = common.signed_post(inbox, user=self.user, data=self.source_as2,
log_data=log_data)
obj.delivered.append(target)
last_success = last
except BaseException as e:
@ -299,7 +302,7 @@ class Webmention(View):
for target in targets:
# fetch target page as AS2 object
try:
self.target_resp = common.get_as2(target)
self.target_resp = common.get_as2(target, user=self.user)
except (requests.HTTPError, BadGateway) as e:
self.target_resp = getattr(e, 'requests_response', None)
if self.target_resp and self.target_resp.status_code // 100 == 2:
@ -327,7 +330,7 @@ class Webmention(View):
if not inbox_url:
# fetch actor as AS object
actor = common.get_as2(actor).json()
actor = common.get_as2(actor, user=self.user).json()
inbox_url = actor.get('inbox')
if not inbox_url: