kopia lustrzana https://github.com/snarfed/bridgy-fed
rodzic
717b068193
commit
4b37674624
33
common.py
33
common.py
|
@ -71,6 +71,7 @@ DOMAIN_BLOCKLIST = frozenset((
|
||||||
'twitter.com',
|
'twitter.com',
|
||||||
) + DOMAINS)
|
) + DOMAINS)
|
||||||
|
|
||||||
|
# currently unused. TODO: remove?
|
||||||
_DEFAULT_SIGNATURE_USER = None
|
_DEFAULT_SIGNATURE_USER = None
|
||||||
|
|
||||||
CACHE_TIME = timedelta(seconds=60)
|
CACHE_TIME = timedelta(seconds=60)
|
||||||
|
@ -87,6 +88,7 @@ def host_url(path_query=None):
|
||||||
return urllib.parse.urljoin(base, path_query)
|
return urllib.parse.urljoin(base, path_query)
|
||||||
|
|
||||||
|
|
||||||
|
# currently unused. TODO: remove?
|
||||||
def default_signature_user():
|
def default_signature_user():
|
||||||
global _DEFAULT_SIGNATURE_USER
|
global _DEFAULT_SIGNATURE_USER
|
||||||
if _DEFAULT_SIGNATURE_USER is None:
|
if _DEFAULT_SIGNATURE_USER is None:
|
||||||
|
@ -94,38 +96,35 @@ def default_signature_user():
|
||||||
return _DEFAULT_SIGNATURE_USER
|
return _DEFAULT_SIGNATURE_USER
|
||||||
|
|
||||||
|
|
||||||
def signed_get(url, **kwargs):
|
def signed_get(url, user, **kwargs):
|
||||||
return signed_request(util.requests_get, url, **kwargs)
|
return signed_request(util.requests_get, url, user, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def signed_post(url, **kwargs):
|
def signed_post(url, user, **kwargs):
|
||||||
return signed_request(util.requests_post, url, **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.
|
"""Wraps requests.* and adds HTTP Signature.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
fn: :func:`util.requests_get` or :func:`util.requests_get`
|
fn: :func:`util.requests_get` or :func:`util.requests_get`
|
||||||
url: str
|
url: str
|
||||||
|
user: :class:`User` to sign request with
|
||||||
data: optional AS2 object
|
data: optional AS2 object
|
||||||
log_data: boolean, whether to log full data object
|
log_data: boolean, whether to log full data object
|
||||||
user: optional :class:`User` to sign request with
|
|
||||||
kwargs: passed through to requests
|
kwargs: passed through to requests
|
||||||
|
|
||||||
Returns: :class:`requests.Response`
|
Returns: :class:`requests.Response`
|
||||||
"""
|
"""
|
||||||
|
assert user
|
||||||
if headers is None:
|
if headers is None:
|
||||||
headers = {}
|
headers = {}
|
||||||
|
|
||||||
# prepare HTTP Signature and headers
|
|
||||||
if not user:
|
|
||||||
user = default_signature_user()
|
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
if log_data:
|
if log_data:
|
||||||
logging.info(f'Sending AS2 object: {json_dumps(data, indent=2)}')
|
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 = copy.deepcopy(headers)
|
||||||
headers.update({
|
headers.update({
|
||||||
|
@ -154,13 +153,15 @@ def signed_request(fn, url, data=None, log_data=True, user=None, headers=None, *
|
||||||
|
|
||||||
# make HTTP request
|
# make HTTP request
|
||||||
kwargs.setdefault('gateway', True)
|
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}')
|
logger.info(f'Got {resp.status_code} headers: {resp.headers}')
|
||||||
|
|
||||||
# handle GET redirects manually so that we generate a new HTTP signature
|
# handle GET redirects manually so that we generate a new HTTP signature
|
||||||
if resp.is_redirect and fn == util.requests_get:
|
if resp.is_redirect and fn == util.requests_get:
|
||||||
return signed_request(fn, resp.headers['Location'], data=data, user=user,
|
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)
|
type = content_type(resp)
|
||||||
if (type and type != 'text/html' and
|
if (type and type != 'text/html' and
|
||||||
(type.startswith('text/') or type.endswith('+json') or type.endswith('/json'))):
|
(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
|
If we raise a werkzeug HTTPException, it will have an additional
|
||||||
requests_response attribute with the last requests.Response we received.
|
requests_response attribute with the last requests.Response we received.
|
||||||
"""
|
"""
|
||||||
|
assert user
|
||||||
|
|
||||||
def _error(resp):
|
def _error(resp):
|
||||||
msg = f"Couldn't fetch {url} as ActivityStreams 2"
|
msg = f"Couldn't fetch {url} as ActivityStreams 2"
|
||||||
logger.warning(msg)
|
logger.warning(msg)
|
||||||
|
@ -217,7 +220,7 @@ def get_as2(url, user=None):
|
||||||
_error(resp)
|
_error(resp)
|
||||||
|
|
||||||
resp = signed_get(urllib.parse.urljoin(resp.url, obj['href']),
|
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):
|
if content_type(resp) in (as2.CONTENT_TYPE, CONTENT_TYPE_LD_PLAIN):
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
|
@ -147,7 +147,7 @@ class FollowCallback(indieauth.Callback):
|
||||||
flash(f"Couldn't find ActivityPub profile link for {addr}")
|
flash(f"Couldn't find ActivityPub profile link for {addr}")
|
||||||
return redirect(f'/user/{domain}/following')
|
return redirect(f'/user/{domain}/following')
|
||||||
|
|
||||||
resp = common.get_as2(as2_url)
|
resp = common.get_as2(as2_url, user=user)
|
||||||
followee = resp.json()
|
followee = resp.json()
|
||||||
id = followee.get('id')
|
id = followee.get('id')
|
||||||
inbox = followee.get('inbox')
|
inbox = followee.get('inbox')
|
||||||
|
@ -165,7 +165,7 @@ class FollowCallback(indieauth.Callback):
|
||||||
'actor': common.host_url(domain),
|
'actor': common.host_url(domain),
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
'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)
|
follow_json = json_dumps(follow_as2, sort_keys=True)
|
||||||
Follower.get_or_create(dest=id, src=domain, status='active',
|
Follower.get_or_create(dest=id, src=domain, status='active',
|
||||||
|
@ -218,7 +218,7 @@ class UnfollowCallback(indieauth.Callback):
|
||||||
if isinstance(followee, str):
|
if isinstance(followee, str):
|
||||||
# fetch as AS2 to get full followee with inbox
|
# fetch as AS2 to get full followee with inbox
|
||||||
followee_id = followee
|
followee_id = followee
|
||||||
resp = common.get_as2(followee_id)
|
resp = common.get_as2(followee_id, user=user)
|
||||||
followee = resp.json()
|
followee = resp.json()
|
||||||
|
|
||||||
inbox = followee.get('inbox')
|
inbox = followee.get('inbox')
|
||||||
|
@ -235,7 +235,7 @@ class UnfollowCallback(indieauth.Callback):
|
||||||
'actor': common.host_url(domain),
|
'actor': common.host_url(domain),
|
||||||
'object': last_follow,
|
'object': last_follow,
|
||||||
}
|
}
|
||||||
common.signed_post(inbox, data=unfollow_as2)
|
common.signed_post(inbox, user=user, data=unfollow_as2)
|
||||||
|
|
||||||
follower.status = 'inactive'
|
follower.status = 'inactive'
|
||||||
follower.put()
|
follower.put()
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from granary import as2
|
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
|
from oauth_dropins.webutil.testutil import requests_response
|
||||||
import requests
|
import requests
|
||||||
from werkzeug.exceptions import BadGateway
|
from werkzeug.exceptions import BadGateway
|
||||||
|
@ -31,6 +31,12 @@ NOT_ACCEPTABLE = requests_response(status=406)
|
||||||
|
|
||||||
|
|
||||||
class CommonTest(testutil.TestCase):
|
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):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.app_context = app.test_request_context('/')
|
self.app_context = app.test_request_context('/')
|
||||||
|
@ -42,7 +48,7 @@ class CommonTest(testutil.TestCase):
|
||||||
|
|
||||||
@mock.patch('requests.get', return_value=AS2)
|
@mock.patch('requests.get', return_value=AS2)
|
||||||
def test_get_as2_direct(self, mock_get):
|
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)
|
self.assertEqual(AS2, resp)
|
||||||
mock_get.assert_has_calls((
|
mock_get.assert_has_calls((
|
||||||
self.as2_req('http://orig'),
|
self.as2_req('http://orig'),
|
||||||
|
@ -50,7 +56,7 @@ class CommonTest(testutil.TestCase):
|
||||||
|
|
||||||
@mock.patch('requests.get', side_effect=[HTML_WITH_AS2, AS2])
|
@mock.patch('requests.get', side_effect=[HTML_WITH_AS2, AS2])
|
||||||
def test_get_as2_via_html(self, mock_get):
|
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)
|
self.assertEqual(AS2, resp)
|
||||||
mock_get.assert_has_calls((
|
mock_get.assert_has_calls((
|
||||||
self.as2_req('http://orig'),
|
self.as2_req('http://orig'),
|
||||||
|
@ -60,17 +66,17 @@ class CommonTest(testutil.TestCase):
|
||||||
@mock.patch('requests.get', return_value=HTML)
|
@mock.patch('requests.get', return_value=HTML)
|
||||||
def test_get_as2_only_html(self, mock_get):
|
def test_get_as2_only_html(self, mock_get):
|
||||||
with self.assertRaises(BadGateway):
|
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)
|
@mock.patch('requests.get', return_value=NOT_ACCEPTABLE)
|
||||||
def test_get_as2_not_acceptable(self, mock_get):
|
def test_get_as2_not_acceptable(self, mock_get):
|
||||||
with self.assertRaises(BadGateway):
|
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)
|
@mock.patch('requests.get', side_effect=requests.exceptions.SSLError)
|
||||||
def test_get_ssl_error(self, mock_get):
|
def test_get_ssl_error(self, mock_get):
|
||||||
with self.assertRaises(BadGateway):
|
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):
|
def test_redirect_wrap_empty(self):
|
||||||
self.assertIsNone(common.redirect_wrap(None))
|
self.assertIsNone(common.redirect_wrap(None))
|
||||||
|
@ -97,7 +103,7 @@ class CommonTest(testutil.TestCase):
|
||||||
}, common.postprocess_as2({
|
}, common.postprocess_as2({
|
||||||
'id': 'xyz',
|
'id': 'xyz',
|
||||||
'inReplyTo': ['foo', 'bar'],
|
'inReplyTo': ['foo', 'bar'],
|
||||||
}, user=User(id='foo.com')))
|
}, user=User(id='site')))
|
||||||
|
|
||||||
def test_postprocess_as2_multiple_url(self):
|
def test_postprocess_as2_multiple_url(self):
|
||||||
with app.test_request_context('/'):
|
with app.test_request_context('/'):
|
||||||
|
@ -108,7 +114,7 @@ class CommonTest(testutil.TestCase):
|
||||||
}, common.postprocess_as2({
|
}, common.postprocess_as2({
|
||||||
'id': 'xyz',
|
'id': 'xyz',
|
||||||
'url': ['foo', 'bar'],
|
'url': ['foo', 'bar'],
|
||||||
}, user=User(id='foo.com')))
|
}, user=User(id='site')))
|
||||||
|
|
||||||
def test_postprocess_as2_multiple_image(self):
|
def test_postprocess_as2_multiple_image(self):
|
||||||
with app.test_request_context('/'):
|
with app.test_request_context('/'):
|
||||||
|
@ -120,30 +126,30 @@ class CommonTest(testutil.TestCase):
|
||||||
}, common.postprocess_as2({
|
}, common.postprocess_as2({
|
||||||
'id': 'xyz',
|
'id': 'xyz',
|
||||||
'image': [{'url': 'http://r/foo'}, {'url': 'http://r/bar'}],
|
'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):
|
def test_postprocess_as2_actor_attributedTo(self):
|
||||||
with app.test_request_context('/'):
|
with app.test_request_context('/'):
|
||||||
self.assert_equals({
|
self.assert_equals({
|
||||||
'actor': {
|
'actor': {
|
||||||
'id': 'baj',
|
'id': 'baj',
|
||||||
'preferredUsername': 'foo.com',
|
'preferredUsername': 'site',
|
||||||
'url': 'http://localhost/r/https://foo.com/',
|
'url': 'http://localhost/r/https://site/',
|
||||||
},
|
},
|
||||||
'attributedTo': [{
|
'attributedTo': [{
|
||||||
'id': 'bar',
|
'id': 'bar',
|
||||||
'preferredUsername': 'foo.com',
|
'preferredUsername': 'site',
|
||||||
'url': 'http://localhost/r/https://foo.com/',
|
'url': 'http://localhost/r/https://site/',
|
||||||
}, {
|
}, {
|
||||||
'id': 'baz',
|
'id': 'baz',
|
||||||
'preferredUsername': 'foo.com',
|
'preferredUsername': 'site',
|
||||||
'url': 'http://localhost/r/https://foo.com/',
|
'url': 'http://localhost/r/https://site/',
|
||||||
}],
|
}],
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
}, common.postprocess_as2({
|
}, common.postprocess_as2({
|
||||||
'attributedTo': [{'id': 'bar'}, {'id': 'baz'}],
|
'attributedTo': [{'id': 'bar'}, {'id': 'baz'}],
|
||||||
'actor': {'id': 'baj'},
|
'actor': {'id': 'baj'},
|
||||||
}, user=User(id='foo.com')))
|
}, user=User(id='site')))
|
||||||
|
|
||||||
def test_postprocess_as2_note(self):
|
def test_postprocess_as2_note(self):
|
||||||
with app.test_request_context('/'):
|
with app.test_request_context('/'):
|
||||||
|
@ -152,9 +158,9 @@ class CommonTest(testutil.TestCase):
|
||||||
'id': 'http://localhost/r/xyz#bridgy-fed-create',
|
'id': 'http://localhost/r/xyz#bridgy-fed-create',
|
||||||
'type': 'Create',
|
'type': 'Create',
|
||||||
'actor': {
|
'actor': {
|
||||||
'id': 'http://localhost/foo.com',
|
'id': 'http://localhost/site',
|
||||||
'url': 'http://localhost/r/https://foo.com/',
|
'url': 'http://localhost/r/https://site/',
|
||||||
'preferredUsername': 'foo.com'
|
'preferredUsername': 'site'
|
||||||
},
|
},
|
||||||
'object': {
|
'object': {
|
||||||
'id': 'http://localhost/r/xyz',
|
'id': 'http://localhost/r/xyz',
|
||||||
|
@ -164,7 +170,7 @@ class CommonTest(testutil.TestCase):
|
||||||
}, common.postprocess_as2({
|
}, common.postprocess_as2({
|
||||||
'id': 'xyz',
|
'id': 'xyz',
|
||||||
'type': 'Note',
|
'type': 'Note',
|
||||||
}, user=User(id='foo.com')))
|
}, user=User(id='site')))
|
||||||
|
|
||||||
def test_host_url(self):
|
def test_host_url(self):
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
|
@ -187,7 +193,7 @@ class CommonTest(testutil.TestCase):
|
||||||
allow_redirects=False),
|
allow_redirects=False),
|
||||||
requests_response(status=200, 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]
|
first = mock_get.call_args_list[0][1]
|
||||||
second = mock_get.call_args_list[1][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',
|
requests_response(status=302, redirected_url='http://second',
|
||||||
allow_redirects=False),
|
allow_redirects=False),
|
||||||
]
|
]
|
||||||
resp = common.signed_post('https://first')
|
resp = common.signed_post('https://first', user=self.user)
|
||||||
mock_post.assert_called_once()
|
mock_post.assert_called_once()
|
||||||
self.assertEqual(302, resp.status_code)
|
self.assertEqual(302, resp.status_code)
|
||||||
|
|
|
@ -37,18 +37,18 @@ FOLLOWEE = {
|
||||||
FOLLOW_ADDRESS = {
|
FOLLOW_ADDRESS = {
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'type': 'Follow',
|
'type': 'Follow',
|
||||||
'id': f'http://localhost/user/snarfed.org/following#2022-01-02T03:04:05-@foo@bar',
|
'id': f'http://localhost/user/alice.com/following#2022-01-02T03:04:05-@foo@bar',
|
||||||
'actor': 'http://localhost/snarfed.org',
|
'actor': 'http://localhost/alice.com',
|
||||||
'object': FOLLOWEE,
|
'object': FOLLOWEE,
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
}
|
}
|
||||||
FOLLOW_URL = copy.deepcopy(FOLLOW_ADDRESS)
|
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 = {
|
UNDO_FOLLOW = {
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'type': 'Undo',
|
'type': 'Undo',
|
||||||
'id': f'http://localhost/user/snarfed.org/following#undo-2022-01-02T03:04:05-https://bar/id',
|
'id': f'http://localhost/user/alice.com/following#undo-2022-01-02T03:04:05-https://bar/id',
|
||||||
'actor': 'http://localhost/snarfed.org',
|
'actor': 'http://localhost/alice.com',
|
||||||
'object': FOLLOW_ADDRESS,
|
'object': FOLLOW_ADDRESS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ class FollowTest(testutil.TestCase):
|
||||||
mock_get.return_value = requests_response('') # IndieAuth endpoint discovery
|
mock_get.return_value = requests_response('') # IndieAuth endpoint discovery
|
||||||
|
|
||||||
resp = self.client.post('/follow/start', data={
|
resp = self.client.post('/follow/start', data={
|
||||||
'me': 'https://snarfed.org',
|
'me': 'https://alice.com',
|
||||||
'address': '@foo@bar',
|
'address': '@foo@bar',
|
||||||
})
|
})
|
||||||
self.assertEqual(302, resp.status_code)
|
self.assertEqual(302, resp.status_code)
|
||||||
|
@ -135,7 +135,7 @@ class FollowTest(testutil.TestCase):
|
||||||
|
|
||||||
def test_callback_address(self, mock_get, mock_post):
|
def test_callback_address(self, mock_get, mock_post):
|
||||||
mock_get.side_effect = (
|
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(''),
|
requests_response(''),
|
||||||
WEBFINGER,
|
WEBFINGER,
|
||||||
self.as2_resp(FOLLOWEE),
|
self.as2_resp(FOLLOWEE),
|
||||||
|
@ -153,22 +153,22 @@ class FollowTest(testutil.TestCase):
|
||||||
self._test_callback('https://bar/actor', FOLLOW_URL, mock_get, mock_post)
|
self._test_callback('https://bar/actor', FOLLOW_URL, mock_get, mock_post)
|
||||||
|
|
||||||
def _test_callback(self, input, expected_follow, 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 = (
|
mock_post.side_effect = (
|
||||||
requests_response('me=https://snarfed.org'),
|
requests_response('me=https://alice.com'),
|
||||||
requests_response('OK'), # AP Follow to inbox
|
requests_response('OK'), # AP Follow to inbox
|
||||||
)
|
)
|
||||||
|
|
||||||
state = util.encode_oauth_state({
|
state = util.encode_oauth_state({
|
||||||
'endpoint': 'http://auth/endpoint',
|
'endpoint': 'http://auth/endpoint',
|
||||||
'me': 'https://snarfed.org',
|
'me': 'https://alice.com',
|
||||||
'state': input,
|
'state': input,
|
||||||
})
|
})
|
||||||
with self.client:
|
with self.client:
|
||||||
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
|
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
|
||||||
self.assertEqual(302, resp.status_code)
|
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>.'],
|
self.assertEqual([f'Followed <a href="https://bar/url">{input}</a>.'],
|
||||||
get_flashed_messages())
|
get_flashed_messages())
|
||||||
|
|
||||||
|
@ -180,25 +180,30 @@ class FollowTest(testutil.TestCase):
|
||||||
self.assertEqual(('http://bar/inbox',), inbox_args)
|
self.assertEqual(('http://bar/inbox',), inbox_args)
|
||||||
self.assert_equals(expected_follow, json_loads(inbox_kwargs['data']))
|
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()
|
followers = Follower.query().fetch()
|
||||||
self.assert_entities_equal(
|
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),
|
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,
|
followers,
|
||||||
ignore=['created', 'updated'])
|
ignore=['created', 'updated'])
|
||||||
|
|
||||||
id = f'http://localhost/user/snarfed.org/following#2022-01-02T03:04:05-{input}'
|
id = f'http://localhost/user/alice.com/following#2022-01-02T03:04:05-{input}'
|
||||||
self.assert_object(id, domains=['snarfed.org'], status='complete',
|
self.assert_object(id, domains=['alice.com'], status='complete',
|
||||||
labels=['user', 'activity'], source_protocol='ui',
|
labels=['user', 'activity'], source_protocol='ui',
|
||||||
as2=expected_follow, as1=as2.to_as1(expected_follow))
|
as2=expected_follow, as1=as2.to_as1(expected_follow))
|
||||||
|
|
||||||
def test_callback_missing_user(self, mock_get, mock_post):
|
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({
|
state = util.encode_oauth_state({
|
||||||
'endpoint': 'http://auth/endpoint',
|
'endpoint': 'http://auth/endpoint',
|
||||||
'me': 'https://snarfed.org',
|
'me': 'https://alice.com',
|
||||||
'state': '@foo@bar',
|
'state': '@foo@bar',
|
||||||
})
|
})
|
||||||
with self.client:
|
with self.client:
|
||||||
|
@ -206,46 +211,46 @@ class FollowTest(testutil.TestCase):
|
||||||
self.assertEqual(400, resp.status_code)
|
self.assertEqual(400, resp.status_code)
|
||||||
|
|
||||||
def test_callback_user_use_instead(self, mock_get, mock_post):
|
def test_callback_user_use_instead(self, mock_get, mock_post):
|
||||||
user = User.get_or_create('www.snarfed.org')
|
user = User.get_or_create('www.alice.com')
|
||||||
User.get_or_create('snarfed.org', use_instead=user.key)
|
User.get_or_create('alice.com', use_instead=user.key)
|
||||||
|
|
||||||
mock_get.side_effect = (
|
mock_get.side_effect = (
|
||||||
requests_response(''),
|
requests_response(''),
|
||||||
self.as2_resp(FOLLOWEE),
|
self.as2_resp(FOLLOWEE),
|
||||||
)
|
)
|
||||||
mock_post.side_effect = (
|
mock_post.side_effect = (
|
||||||
requests_response('me=https://snarfed.org'),
|
requests_response('me=https://alice.com'),
|
||||||
requests_response('OK'), # AP Follow to inbox
|
requests_response('OK'), # AP Follow to inbox
|
||||||
)
|
)
|
||||||
|
|
||||||
state = util.encode_oauth_state({
|
state = util.encode_oauth_state({
|
||||||
'endpoint': 'http://auth/endpoint',
|
'endpoint': 'http://auth/endpoint',
|
||||||
'me': 'https://snarfed.org',
|
'me': 'https://alice.com',
|
||||||
'state': 'https://bar/actor',
|
'state': 'https://bar/actor',
|
||||||
})
|
})
|
||||||
with self.client:
|
with self.client:
|
||||||
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
|
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
|
||||||
self.assertEqual(302, resp.status_code)
|
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 = {
|
expected_follow = {
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'type': 'Follow',
|
'type': 'Follow',
|
||||||
'id': id,
|
'id': id,
|
||||||
'actor': 'http://localhost/www.snarfed.org',
|
'actor': 'http://localhost/www.alice.com',
|
||||||
'object': FOLLOWEE,
|
'object': FOLLOWEE,
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
}
|
}
|
||||||
followers = Follower.query().fetch()
|
followers = Follower.query().fetch()
|
||||||
self.assert_entities_equal(
|
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),
|
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,
|
followers,
|
||||||
ignore=['created', 'updated'])
|
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',
|
labels=['user', 'activity'], source_protocol='ui',
|
||||||
as2=expected_follow, as1=as2.to_as1(expected_follow))
|
as2=expected_follow, as1=as2.to_as1(expected_follow))
|
||||||
|
|
||||||
|
@ -257,15 +262,15 @@ class UnfollowTest(testutil.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.follower = Follower(
|
self.follower = Follower(
|
||||||
id='https://bar/id snarfed.org', last_follow=json_dumps(FOLLOW_ADDRESS),
|
id='https://bar/id alice.com', last_follow=json_dumps(FOLLOW_ADDRESS),
|
||||||
src='snarfed.org', dest='https://bar/id', status='active',
|
src='alice.com', dest='https://bar/id', status='active',
|
||||||
).put()
|
).put()
|
||||||
|
|
||||||
def test_start(self, mock_get, _):
|
def test_start(self, mock_get, _):
|
||||||
mock_get.return_value = requests_response('') # IndieAuth endpoint discovery
|
mock_get.return_value = requests_response('') # IndieAuth endpoint discovery
|
||||||
|
|
||||||
resp = self.client.post('/unfollow/start', data={
|
resp = self.client.post('/unfollow/start', data={
|
||||||
'me': 'https://snarfed.org',
|
'me': 'https://alice.com',
|
||||||
'key': self.follower.id(),
|
'key': self.follower.id(),
|
||||||
})
|
})
|
||||||
self.assertEqual(302, resp.status_code)
|
self.assertEqual(302, resp.status_code)
|
||||||
|
@ -284,7 +289,7 @@ class UnfollowTest(testutil.TestCase):
|
||||||
})
|
})
|
||||||
follower.put()
|
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 = (
|
mock_get.side_effect = (
|
||||||
requests_response(''),
|
requests_response(''),
|
||||||
self.as2_resp(FOLLOWEE), # fetch to discover inbox
|
self.as2_resp(FOLLOWEE), # fetch to discover inbox
|
||||||
|
@ -296,21 +301,21 @@ class UnfollowTest(testutil.TestCase):
|
||||||
self._test_callback(undo, mock_get, mock_post)
|
self._test_callback(undo, mock_get, mock_post)
|
||||||
|
|
||||||
def _test_callback(self, expected_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 = (
|
mock_post.side_effect = (
|
||||||
requests_response('me=https://snarfed.org'),
|
requests_response('me=https://alice.com'),
|
||||||
requests_response('OK'), # AP Undo Follow to inbox
|
requests_response('OK'), # AP Undo Follow to inbox
|
||||||
)
|
)
|
||||||
|
|
||||||
state = util.encode_oauth_state({
|
state = util.encode_oauth_state({
|
||||||
'endpoint': 'http://auth/endpoint',
|
'endpoint': 'http://auth/endpoint',
|
||||||
'me': 'https://snarfed.org',
|
'me': 'https://alice.com',
|
||||||
'state': self.follower.id(),
|
'state': self.follower.id(),
|
||||||
})
|
})
|
||||||
with self.client:
|
with self.client:
|
||||||
resp = self.client.get(f'/unfollow/callback?code=my_code&state={state}')
|
resp = self.client.get(f'/unfollow/callback?code=my_code&state={state}')
|
||||||
self.assertEqual(302, resp.status_code)
|
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>.'],
|
self.assertEqual([f'Unfollowed <a href="https://bar/url">bar/url</a>.'],
|
||||||
get_flashed_messages())
|
get_flashed_messages())
|
||||||
|
|
||||||
|
@ -318,22 +323,27 @@ class UnfollowTest(testutil.TestCase):
|
||||||
self.assertEqual(('http://bar/inbox',), inbox_args)
|
self.assertEqual(('http://bar/inbox',), inbox_args)
|
||||||
self.assert_equals(expected_undo, json_loads(inbox_kwargs['data']))
|
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.assertEqual('inactive', follower.status)
|
||||||
|
|
||||||
self.assert_object(
|
self.assert_object(
|
||||||
'http://localhost/user/snarfed.org/following#undo-2022-01-02T03:04:05-https://bar/id',
|
'http://localhost/user/alice.com/following#undo-2022-01-02T03:04:05-https://bar/id',
|
||||||
domains=['snarfed.org'], status='complete',
|
domains=['alice.com'], status='complete',
|
||||||
source_protocol='ui', labels=['user', 'activity'],
|
source_protocol='ui', labels=['user', 'activity'],
|
||||||
as2=expected_undo, as1=as2.to_as1(expected_undo))
|
as2=expected_undo, as1=as2.to_as1(expected_undo))
|
||||||
|
|
||||||
def test_callback_user_use_instead(self, mock_get, mock_post):
|
def test_callback_user_use_instead(self, mock_get, mock_post):
|
||||||
user = User.get_or_create('www.snarfed.org')
|
user = User.get_or_create('www.alice.com')
|
||||||
User.get_or_create('snarfed.org', use_instead=user.key)
|
User.get_or_create('alice.com', use_instead=user.key)
|
||||||
|
|
||||||
self.follower = Follower(
|
self.follower = Follower(
|
||||||
id='https://bar/id www.snarfed.org', last_follow=json_dumps(FOLLOW_ADDRESS),
|
id='https://bar/id www.alice.com', last_follow=json_dumps(FOLLOW_ADDRESS),
|
||||||
src='www.snarfed.org', dest='https://bar/id', status='active',
|
src='www.alice.com', dest='https://bar/id', status='active',
|
||||||
).put()
|
).put()
|
||||||
|
|
||||||
mock_get.side_effect = (
|
mock_get.side_effect = (
|
||||||
|
@ -341,26 +351,26 @@ class UnfollowTest(testutil.TestCase):
|
||||||
self.as2_resp(FOLLOWEE),
|
self.as2_resp(FOLLOWEE),
|
||||||
)
|
)
|
||||||
mock_post.side_effect = (
|
mock_post.side_effect = (
|
||||||
requests_response('me=https://snarfed.org'),
|
requests_response('me=https://alice.com'),
|
||||||
requests_response('OK'), # AP Undo Follow to inbox
|
requests_response('OK'), # AP Undo Follow to inbox
|
||||||
)
|
)
|
||||||
|
|
||||||
state = util.encode_oauth_state({
|
state = util.encode_oauth_state({
|
||||||
'endpoint': 'http://auth/endpoint',
|
'endpoint': 'http://auth/endpoint',
|
||||||
'me': 'https://snarfed.org',
|
'me': 'https://alice.com',
|
||||||
'state': self.follower.id(),
|
'state': self.follower.id(),
|
||||||
})
|
})
|
||||||
with self.client:
|
with self.client:
|
||||||
resp = self.client.get(f'/unfollow/callback?code=my_code&state={state}')
|
resp = self.client.get(f'/unfollow/callback?code=my_code&state={state}')
|
||||||
self.assertEqual(302, resp.status_code)
|
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 = {
|
expected_undo = {
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
'type': 'Undo',
|
'type': 'Undo',
|
||||||
'id': id,
|
'id': id,
|
||||||
'actor': 'http://localhost/www.snarfed.org',
|
'actor': 'http://localhost/www.alice.com',
|
||||||
'object': FOLLOW_ADDRESS,
|
'object': FOLLOW_ADDRESS,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,9 +378,9 @@ class UnfollowTest(testutil.TestCase):
|
||||||
self.assertEqual(('http://bar/inbox',), inbox_args)
|
self.assertEqual(('http://bar/inbox',), inbox_args)
|
||||||
self.assert_equals(expected_undo, json_loads(inbox_kwargs['data']))
|
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.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'],
|
source_protocol='ui', labels=['user', 'activity'],
|
||||||
as2=expected_undo, as1=as2.to_as1(expected_undo))
|
as2=expected_undo, as1=as2.to_as1(expected_undo))
|
||||||
|
|
|
@ -7,7 +7,7 @@ from urllib.parse import urlencode
|
||||||
import feedparser
|
import feedparser
|
||||||
from granary import as2, atom, microformats2
|
from granary import as2, atom, microformats2
|
||||||
from httpsig.sign import HeaderSigner
|
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_config import tasks_client
|
||||||
from oauth_dropins.webutil.appengine_info import APP_ID
|
from oauth_dropins.webutil.appengine_info import APP_ID
|
||||||
from oauth_dropins.webutil.testutil import requests_response
|
from oauth_dropins.webutil.testutil import requests_response
|
||||||
|
@ -18,7 +18,6 @@ import activitypub
|
||||||
from common import (
|
from common import (
|
||||||
CONNEG_HEADERS_AS2_HTML,
|
CONNEG_HEADERS_AS2_HTML,
|
||||||
CONTENT_TYPE_HTML,
|
CONTENT_TYPE_HTML,
|
||||||
default_signature_user,
|
|
||||||
redirect_unwrap,
|
redirect_unwrap,
|
||||||
)
|
)
|
||||||
from models import Follower, Object, Target, User
|
from models import Follower, Object, Target, User
|
||||||
|
@ -108,8 +107,8 @@ REPOST_AS2 = {
|
||||||
class WebmentionTest(testutil.TestCase):
|
class WebmentionTest(testutil.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
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("""\
|
self.orig_html_as2 = requests_response("""\
|
||||||
<html>
|
<html>
|
||||||
<meta>
|
<meta>
|
||||||
|
@ -484,7 +483,7 @@ class WebmentionTest(testutil.TestCase):
|
||||||
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
||||||
|
|
||||||
rsa_key = kwargs['auth'].header_signer._rsa._key
|
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',
|
self.assert_object('http://a/reply',
|
||||||
domains=['a'],
|
domains=['a'],
|
||||||
|
@ -603,13 +602,12 @@ class WebmentionTest(testutil.TestCase):
|
||||||
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
||||||
|
|
||||||
rsa_key = kwargs['auth'].header_signer._rsa._key
|
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:]:
|
for args, kwargs in mock_get.call_args_list[1:]:
|
||||||
with self.subTest(url=args[0]):
|
with self.subTest(url=args[0]):
|
||||||
rsa_key = kwargs['auth'].header_signer._rsa._key
|
rsa_key = kwargs['auth'].header_signer._rsa._key
|
||||||
self.assertEqual(default_signature_user().private_pem(),
|
self.assertEqual(self.user_a.private_pem(), rsa_key.exportKey())
|
||||||
rsa_key.exportKey())
|
|
||||||
|
|
||||||
self.assert_object('http://a/repost',
|
self.assert_object('http://a/repost',
|
||||||
domains=['a'],
|
domains=['a'],
|
||||||
|
@ -909,7 +907,7 @@ class WebmentionTest(testutil.TestCase):
|
||||||
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
||||||
|
|
||||||
rsa_key = kwargs['auth'].header_signer._rsa._key
|
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',
|
self.assert_object('http://a/follow',
|
||||||
domains=['a'],
|
domains=['a'],
|
||||||
|
@ -931,8 +929,8 @@ class WebmentionTest(testutil.TestCase):
|
||||||
self.assertEqual(self.follow_as2, json_loads(followers[0].last_follow))
|
self.assertEqual(self.follow_as2, json_loads(followers[0].last_follow))
|
||||||
|
|
||||||
def test_follow_no_actor(self, mock_get, mock_post):
|
def test_follow_no_actor(self, mock_get, mock_post):
|
||||||
self.user.actor_as2 = json_dumps(self.follow_as2['actor'])
|
self.user_orig.actor_as2 = json_dumps(self.follow_as2['actor'])
|
||||||
self.user.put()
|
self.user_orig.put()
|
||||||
|
|
||||||
html = self.follow_html.replace(
|
html = self.follow_html.replace(
|
||||||
'<a class="p-author h-card" href="https://orig">Ms. ☕ Baz</a>', '')
|
'<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'])
|
self.assert_equals(as2.CONTENT_TYPE, headers['Content-Type'])
|
||||||
|
|
||||||
rsa_key = kwargs['auth'].header_signer._rsa._key
|
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',
|
self.assert_object('http://a/follow#2',
|
||||||
domains=['a'],
|
domains=['a'],
|
||||||
|
@ -1033,7 +1031,7 @@ class WebmentionTest(testutil.TestCase):
|
||||||
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
||||||
|
|
||||||
rsa_key = kwargs['auth'].header_signer._rsa._key
|
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',
|
self.assert_object('http://a/follow',
|
||||||
domains=['a'],
|
domains=['a'],
|
||||||
|
@ -1132,3 +1130,9 @@ class WebmentionTest(testutil.TestCase):
|
||||||
object_ids=['https://orig'],
|
object_ids=['https://orig'],
|
||||||
labels=['user', 'activity'],
|
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())
|
||||||
|
|
|
@ -55,8 +55,9 @@ class TestCase(unittest.TestCase, testutil.Asserts):
|
||||||
**common.CONNEG_HEADERS_AS2_HTML,
|
**common.CONNEG_HEADERS_AS2_HTML,
|
||||||
**kwargs.pop('headers', {}),
|
**kwargs.pop('headers', {}),
|
||||||
}
|
}
|
||||||
return self.req(url, auth=ANY, headers=headers, allow_redirects=False,
|
return self.req(url, data=None, auth=ANY, headers=headers,
|
||||||
**kwargs)
|
allow_redirects=False, **kwargs)
|
||||||
|
|
||||||
def as2_resp(self, obj):
|
def as2_resp(self, obj):
|
||||||
return requests_response(obj, content_type=as2.CONTENT_TYPE)
|
return requests_response(obj, content_type=as2.CONTENT_TYPE)
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,10 @@ class Webmention(View):
|
||||||
))
|
))
|
||||||
logger.info(f'Converted webmention to AS1: {type_label}: {json_dumps(self.source_as1, indent=2)}')
|
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()
|
ret = self.try_activitypub()
|
||||||
return ret or 'No ActivityPub targets'
|
return ret or 'No ActivityPub targets'
|
||||||
|
|
||||||
|
@ -210,8 +213,8 @@ class Webmention(View):
|
||||||
last_follow=json_dumps(self.source_as2))
|
last_follow=json_dumps(self.source_as2))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
last = common.signed_post(inbox, data=self.source_as2,
|
last = common.signed_post(inbox, user=self.user, data=self.source_as2,
|
||||||
log_data=log_data, user=self.user)
|
log_data=log_data)
|
||||||
obj.delivered.append(target)
|
obj.delivered.append(target)
|
||||||
last_success = last
|
last_success = last
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
|
@ -299,7 +302,7 @@ class Webmention(View):
|
||||||
for target in targets:
|
for target in targets:
|
||||||
# fetch target page as AS2 object
|
# fetch target page as AS2 object
|
||||||
try:
|
try:
|
||||||
self.target_resp = common.get_as2(target)
|
self.target_resp = common.get_as2(target, user=self.user)
|
||||||
except (requests.HTTPError, BadGateway) as e:
|
except (requests.HTTPError, BadGateway) as e:
|
||||||
self.target_resp = getattr(e, 'requests_response', None)
|
self.target_resp = getattr(e, 'requests_response', None)
|
||||||
if self.target_resp and self.target_resp.status_code // 100 == 2:
|
if self.target_resp and self.target_resp.status_code // 100 == 2:
|
||||||
|
@ -327,7 +330,7 @@ class Webmention(View):
|
||||||
|
|
||||||
if not inbox_url:
|
if not inbox_url:
|
||||||
# fetch actor as AS object
|
# 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')
|
inbox_url = actor.get('inbox')
|
||||||
|
|
||||||
if not inbox_url:
|
if not inbox_url:
|
||||||
|
|
Ładowanie…
Reference in New Issue