2023-02-01 21:19:41 +00:00
""" Unit tests for activitypub.py. """
2023-02-15 18:57:11 +00:00
from base64 import b64encode
2017-08-23 15:14:51 +00:00
import copy
2023-01-20 15:02:55 +00:00
from datetime import datetime , timedelta
2023-02-15 18:57:11 +00:00
from hashlib import sha256
2023-02-16 04:10:17 +00:00
import logging
2023-05-24 04:30:57 +00:00
from unittest import skip
2023-06-20 18:22:54 +00:00
from unittest . mock import patch
2017-08-13 07:12:16 +00:00
2023-03-20 21:28:14 +00:00
from flask import g
2023-02-15 18:57:11 +00:00
from google . cloud import ndb
2023-02-24 03:17:26 +00:00
from granary import as2 , microformats2
2023-02-16 06:05:14 +00:00
from httpsig import HeaderSigner
2017-08-24 14:41:46 +00:00
from oauth_dropins . webutil . testutil import requests_response
2023-09-27 20:55:16 +00:00
from oauth_dropins . webutil . util import domain_from_link , json_dumps , json_loads
from oauth_dropins . webutil import util
2017-08-13 07:12:16 +00:00
import requests
2021-09-01 15:19:38 +00:00
from urllib3 . exceptions import ReadTimeoutError
2024-02-28 18:57:30 +00:00
from werkzeug . exceptions import BadGateway , BadRequest
2017-08-13 07:12:16 +00:00
2023-05-30 23:36:18 +00:00
# import first so that Fake is defined before URL routes are registered
2024-01-06 21:59:31 +00:00
from . import testutil
2024-04-21 15:36:03 +00:00
from . testutil import ExplicitEnableFake , Fake , TestCase
2023-05-30 23:36:18 +00:00
2017-08-13 07:12:16 +00:00
import activitypub
2023-11-24 17:20:52 +00:00
from activitypub import (
ActivityPub ,
2024-01-06 21:59:31 +00:00
instance_actor ,
2023-11-24 17:20:52 +00:00
postprocess_as2 ,
postprocess_as2_actor ,
)
2023-11-24 06:08:19 +00:00
from atproto import ATProto
2017-08-15 06:07:24 +00:00
import common
2023-05-26 23:07:36 +00:00
from models import Follower , Object
2023-03-08 21:10:41 +00:00
import protocol
2023-05-27 00:40:29 +00:00
from web import Web
2023-05-26 23:07:36 +00:00
2023-06-14 20:46:13 +00:00
# have to import module, not attrs, to avoid circular import
2023-06-02 19:55:07 +00:00
from . import test_web
2023-09-23 20:53:17 +00:00
from . import test_webfinger
2023-06-02 19:55:07 +00:00
2023-02-08 18:48:05 +00:00
ACTOR = {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' https://mas.to/users/swentel ' ,
2023-02-08 18:48:05 +00:00
' type ' : ' Person ' ,
2023-03-19 22:43:55 +00:00
' inbox ' : ' http://mas.to/inbox ' ,
2023-02-14 16:25:41 +00:00
' name ' : ' Mrs. ☕ Foo ' ,
2023-03-19 22:43:55 +00:00
' icon ' : { ' type ' : ' Image ' , ' url ' : ' https://user.com/me.jpg ' } ,
2023-06-16 04:22:20 +00:00
' image ' : { ' type ' : ' Image ' , ' url ' : ' https://user.com/me.jpg ' } ,
2023-02-08 18:48:05 +00:00
}
2023-11-23 01:59:18 +00:00
ACTOR_AS1 = as2 . to_as1 ( ACTOR )
2023-06-03 15:36:55 +00:00
ACTOR_BASE = {
' @context ' : [
' https://www.w3.org/ns/activitystreams ' ,
' https://w3id.org/security/v1 ' ,
] ,
2024-01-11 23:16:33 +00:00
' type ' : ' Application ' ,
2023-06-02 19:55:07 +00:00
' id ' : ' http://localhost/user.com ' ,
2023-06-03 15:36:55 +00:00
' url ' : ' http://localhost/r/https://user.com/ ' ,
2023-06-02 19:55:07 +00:00
' preferredUsername ' : ' user.com ' ,
2024-01-11 23:16:33 +00:00
' summary ' : ' [<a href= " https://fed.brid.gy/web/user.com " >bridged</a> from <a href= " https://user.com/ " >user.com</a> by <a href= " https://fed.brid.gy/ " >Bridgy Fed</a>] ' ,
2023-06-02 19:55:07 +00:00
' inbox ' : ' http://localhost/user.com/inbox ' ,
' outbox ' : ' http://localhost/user.com/outbox ' ,
' following ' : ' http://localhost/user.com/following ' ,
' followers ' : ' http://localhost/user.com/followers ' ,
' endpoints ' : {
2023-09-26 23:43:48 +00:00
' sharedInbox ' : ' https://web.brid.gy/ap/sharedInbox ' ,
2023-06-02 19:55:07 +00:00
} ,
' publicKey ' : {
2023-08-26 16:21:53 +00:00
' id ' : ' http://localhost/user.com#key ' ,
2023-06-02 19:55:07 +00:00
' owner ' : ' http://localhost/user.com ' ,
' publicKeyPem ' : ' populated in setUp() ' ,
} ,
}
2023-06-05 03:58:21 +00:00
ACTOR_BASE_FULL = {
* * ACTOR_BASE ,
' name ' : ' Ms. ☕ Baz ' ,
' attachment ' : [ {
' name ' : ' Web site ' ,
' type ' : ' PropertyValue ' ,
2023-10-24 23:02:16 +00:00
' value ' : ' <a rel= " me " href= " https://user.com " ><span class= " invisible " >https://</span>user.com</a> ' ,
2023-06-05 03:58:21 +00:00
} ] ,
}
2023-09-23 20:53:17 +00:00
ACTOR_FAKE = {
2023-09-26 23:43:48 +00:00
' @context ' : [
' https://www.w3.org/ns/activitystreams ' ,
' https://w3id.org/security/v1 ' ,
] ,
2023-09-23 20:53:17 +00:00
' type ' : ' Person ' ,
2023-09-26 23:43:48 +00:00
' id ' : ' https://fa.brid.gy/ap/fake:user ' ,
' url ' : ' https://fa.brid.gy/r/fake:user ' ,
' inbox ' : ' https://fa.brid.gy/ap/fake:user/inbox ' ,
' outbox ' : ' https://fa.brid.gy/ap/fake:user/outbox ' ,
' following ' : ' https://fa.brid.gy/ap/fake:user/following ' ,
' followers ' : ' https://fa.brid.gy/ap/fake:user/followers ' ,
' endpoints ' : { ' sharedInbox ' : ' https://fa.brid.gy/ap/sharedInbox ' } ,
2023-11-21 23:18:21 +00:00
' preferredUsername ' : ' fake:handle:user ' ,
2023-09-23 20:53:17 +00:00
' summary ' : ' ' ,
' publicKey ' : {
2023-11-08 19:53:32 +00:00
' id ' : ' https://fa.brid.gy/ap/fake:user#key ' ,
' owner ' : ' https://fa.brid.gy/ap/fake:user ' ,
2023-09-23 20:53:17 +00:00
' publicKeyPem ' : ' populated in setUp() ' ,
} ,
}
2018-10-15 15:09:36 +00:00
REPLY_OBJECT = {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' type ' : ' Note ' ,
' content ' : ' A ☕ reply ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' http://mas.to/reply/id ' ,
' url ' : ' http://mas.to/reply ' ,
2023-11-25 22:50:08 +00:00
' author ' : ' https://mas.to/users/swentel ' ,
2023-03-19 22:43:55 +00:00
' inReplyTo ' : ' https://user.com/post ' ,
2022-08-24 00:37:50 +00:00
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
2018-10-15 15:09:36 +00:00
}
2018-10-17 14:00:31 +00:00
REPLY_OBJECT_WRAPPED = copy . deepcopy ( REPLY_OBJECT )
2023-03-19 22:43:55 +00:00
REPLY_OBJECT_WRAPPED [ ' inReplyTo ' ] = ' http://localhost/r/https://user.com/post '
2018-10-21 22:28:42 +00:00
REPLY = {
2018-10-15 15:09:36 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' type ' : ' Create ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' http://mas.to/reply/as2 ' ,
2018-10-15 15:09:36 +00:00
' object ' : REPLY_OBJECT ,
}
2022-11-14 05:46:26 +00:00
NOTE_OBJECT = {
2019-04-16 14:59:29 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' type ' : ' Note ' ,
2022-11-14 05:46:26 +00:00
' content ' : ' ☕ just a normal post ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' http://mas.to/note/id ' ,
' url ' : ' http://mas.to/note ' ,
2022-08-24 00:37:50 +00:00
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
2019-04-16 14:59:29 +00:00
' cc ' : [
2023-03-19 22:43:55 +00:00
' https://mas.to/author/followers ' ,
2019-04-16 14:59:29 +00:00
' https://masto.foo/@other ' ,
' http://localhost/target ' , # redirect-wrapped
] ,
}
2022-11-14 05:46:26 +00:00
NOTE = {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' type ' : ' Create ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' http://mas.to/note/as2 ' ,
2022-11-17 02:53:49 +00:00
' actor ' : ' https://masto.foo/@author ' ,
2022-11-14 05:46:26 +00:00
' object ' : NOTE_OBJECT ,
}
MENTION_OBJECT = copy . deepcopy ( NOTE_OBJECT )
2022-11-17 02:53:49 +00:00
MENTION_OBJECT . update ( {
2023-03-19 22:43:55 +00:00
' id ' : ' http://mas.to/mention/id ' ,
' url ' : ' http://mas.to/mention ' ,
2022-11-17 02:53:49 +00:00
' tag ' : [ {
' type ' : ' Mention ' ,
' href ' : ' https://masto.foo/@other ' ,
' name ' : ' @other@masto.foo ' ,
} , {
' type ' : ' Mention ' ,
2022-12-10 17:01:04 +00:00
' href ' : ' http://localhost/tar.get ' , # redirect-wrapped
' name ' : ' @tar.get@tar.get ' ,
2022-11-17 02:53:49 +00:00
} ] ,
} )
2019-04-16 14:59:29 +00:00
MENTION = {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' type ' : ' Create ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' http://mas.to/mention/as2 ' ,
2019-04-16 14:59:29 +00:00
' object ' : MENTION_OBJECT ,
}
2018-10-17 14:49:04 +00:00
# based on example Mastodon like:
# https://github.com/snarfed/bridgy-fed/issues/4#issuecomment-334212362
# (reposts are very similar)
2018-10-21 22:28:42 +00:00
LIKE = {
2018-10-17 14:49:04 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' http://mas.to/like#ok ' ,
2018-10-17 14:49:04 +00:00
' type ' : ' Like ' ,
2023-03-19 22:43:55 +00:00
' object ' : ' https://user.com/post ' ,
2023-07-11 20:12:49 +00:00
' actor ' : ' https://mas.to/actor ' ,
2018-10-17 14:49:04 +00:00
}
2018-10-21 22:28:42 +00:00
LIKE_WRAPPED = copy . deepcopy ( LIKE )
2023-03-19 22:43:55 +00:00
LIKE_WRAPPED [ ' object ' ] = ' http://localhost/r/https://user.com/post '
2023-05-31 20:17:17 +00:00
LIKE_ACTOR = {
2018-10-23 14:52:30 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-07-11 20:12:49 +00:00
' id ' : ' https://mas.to/actor ' ,
2018-10-23 14:52:30 +00:00
' type ' : ' Person ' ,
' name ' : ' Ms. Actor ' ,
' preferredUsername ' : ' msactor ' ,
2023-05-31 20:17:17 +00:00
' icon ' : { ' type ' : ' Image ' , ' url ' : ' https://user.com/pic.jpg ' } ,
' image ' : [
{ ' type ' : ' Image ' , ' url ' : ' https://user.com/thumb.jpg ' } ,
{ ' type ' : ' Image ' , ' url ' : ' https://user.com/pic.jpg ' } ,
] ,
}
LIKE_WITH_ACTOR = {
* * LIKE ,
' actor ' : LIKE_ACTOR ,
2018-10-23 14:52:30 +00:00
}
2018-10-21 22:28:42 +00:00
2023-03-19 23:21:44 +00:00
# repost, should be delivered to followers if object is a fediverse post,
# translated to webmention if object is an indieweb post
2023-02-08 18:48:05 +00:00
REPOST = {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-02-19 01:53:27 +00:00
' id ' : ' https://mas.to/users/alice/statuses/654/activity ' ,
2023-02-08 18:48:05 +00:00
' type ' : ' Announce ' ,
' actor ' : ACTOR [ ' id ' ] ,
2023-02-12 03:58:07 +00:00
' object ' : NOTE_OBJECT [ ' id ' ] ,
2023-02-08 18:48:05 +00:00
' published ' : ' 2023-02-08T17:44:16Z ' ,
2023-10-26 19:02:57 +00:00
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
2022-11-17 02:53:49 +00:00
}
2023-02-12 03:58:07 +00:00
REPOST_FULL = {
* * REPOST ,
' actor ' : ACTOR ,
' object ' : NOTE_OBJECT ,
}
2023-02-08 18:48:05 +00:00
2018-10-23 14:52:30 +00:00
FOLLOW = {
2018-10-21 22:28:42 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' https://mas.to/6d1a ' ,
2018-10-21 22:28:42 +00:00
' type ' : ' Follow ' ,
2022-11-17 02:53:49 +00:00
' actor ' : ACTOR [ ' id ' ] ,
2023-03-19 22:43:55 +00:00
' object ' : ' https://user.com/ ' ,
2018-10-21 22:28:42 +00:00
}
2018-10-23 14:52:30 +00:00
FOLLOW_WRAPPED = copy . deepcopy ( FOLLOW )
2023-03-19 22:43:55 +00:00
FOLLOW_WRAPPED [ ' object ' ] = ' http://localhost/user.com '
2018-10-23 14:52:30 +00:00
FOLLOW_WITH_ACTOR = copy . deepcopy ( FOLLOW )
2022-11-17 02:53:49 +00:00
FOLLOW_WITH_ACTOR [ ' actor ' ] = ACTOR
2018-10-23 14:52:30 +00:00
FOLLOW_WRAPPED_WITH_ACTOR = copy . deepcopy ( FOLLOW_WRAPPED )
2022-11-17 02:53:49 +00:00
FOLLOW_WRAPPED_WITH_ACTOR [ ' actor ' ] = ACTOR
2023-01-19 15:00:13 +00:00
FOLLOW_WITH_OBJECT = copy . deepcopy ( FOLLOW )
FOLLOW_WITH_OBJECT [ ' object ' ] = ACTOR
2018-10-23 14:52:30 +00:00
2023-05-30 02:01:25 +00:00
ACCEPT_FOLLOW = copy . deepcopy ( FOLLOW_WITH_ACTOR )
2023-06-25 19:54:44 +00:00
del ACCEPT_FOLLOW [ ' @context ' ]
2023-05-30 02:01:25 +00:00
del ACCEPT_FOLLOW [ ' actor ' ] [ ' @context ' ]
ACCEPT_FOLLOW [ ' actor ' ] [ ' image ' ] = { ' type ' : ' Image ' , ' url ' : ' https://user.com/me.jpg ' }
2023-06-25 19:54:44 +00:00
ACCEPT_FOLLOW [ ' object ' ] = ' http://localhost/user.com '
2018-10-21 22:28:42 +00:00
ACCEPT = {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' type ' : ' Accept ' ,
2024-04-19 20:16:48 +00:00
' id ' : ' http://localhost/r/user.com/followers#accept-https://mas.to/6d1a ' ,
2023-03-19 22:43:55 +00:00
' actor ' : ' http://localhost/user.com ' ,
2018-10-21 22:28:42 +00:00
' object ' : {
2023-10-11 23:17:43 +00:00
' type ' : ' Follow ' ,
' id ' : ' https://mas.to/6d1a ' ,
' object ' : ' http://localhost/user.com ' ,
' actor ' : ' https://mas.to/users/swentel ' ,
2023-10-24 17:46:57 +00:00
' url ' : ' https://mas.to/users/swentel#followed-user.com ' ,
2023-10-26 19:02:57 +00:00
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
2023-03-21 02:17:55 +00:00
} ,
2023-10-26 19:02:57 +00:00
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
2018-10-21 22:28:42 +00:00
}
2018-10-15 15:09:36 +00:00
2019-08-01 14:32:45 +00:00
UNDO_FOLLOW_WRAPPED = {
2023-09-27 20:55:16 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' id ' : ' https://mas.to/6d1b ' ,
' type ' : ' Undo ' ,
' actor ' : ' https://mas.to/users/swentel ' ,
' object ' : FOLLOW_WRAPPED ,
2019-08-01 14:32:45 +00:00
}
2020-03-01 04:28:53 +00:00
DELETE = {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' https://mas.to/users/swentel#delete ' ,
2020-03-01 04:28:53 +00:00
' type ' : ' Delete ' ,
2023-03-19 22:43:55 +00:00
' actor ' : ' https://mas.to/users/swentel ' ,
' object ' : ' https://mas.to/users/swentel ' ,
2020-03-01 04:28:53 +00:00
}
2023-01-26 03:44:48 +00:00
UPDATE_PERSON = {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' id ' : ' https://a/person#update ' ,
' type ' : ' Update ' ,
2023-03-19 22:43:55 +00:00
' actor ' : ' https://mas.to/users/swentel ' ,
2023-01-26 03:44:48 +00:00
' object ' : {
' type ' : ' Person ' ,
' id ' : ' https://a/person ' ,
} ,
}
UPDATE_NOTE = {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' id ' : ' https://a/note#update ' ,
' type ' : ' Update ' ,
2023-03-19 22:43:55 +00:00
' actor ' : ' https://mas.to/users/swentel ' ,
2023-01-26 03:44:48 +00:00
' object ' : {
' type ' : ' Note ' ,
' id ' : ' https://a/note ' ,
} ,
}
2023-02-19 01:53:27 +00:00
WEBMENTION_DISCOVERY = requests_response (
' <html><head><link rel= " webmention " href= " /webmention " ></html> ' )
2023-03-08 21:10:41 +00:00
HTML = requests_response ( ' <html></html> ' , headers = {
' Content-Type ' : common . CONTENT_TYPE_HTML ,
} )
HTML_WITH_AS2 = requests_response ( """ \
< html > < meta >
< link href = ' http://as2 ' rel = ' alternate ' type = ' application/activity+json ' >
< / meta > < / html >
""" , headers= {
' Content-Type ' : common . CONTENT_TYPE_HTML ,
} )
AS2_OBJ = { ' foo ' : [ ' bar ' ] }
AS2 = requests_response ( AS2_OBJ , headers = {
' Content-Type ' : as2 . CONTENT_TYPE ,
} )
NOT_ACCEPTABLE = requests_response ( status = 406 )
2020-03-01 04:28:53 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.post ' )
@patch ( ' requests.get ' )
@patch ( ' requests.head ' )
2023-05-26 23:07:36 +00:00
class ActivityPubTest ( TestCase ) :
2017-08-13 07:12:16 +00:00
2023-02-08 02:25:24 +00:00
def setUp ( self ) :
super ( ) . setUp ( )
2023-06-16 04:22:20 +00:00
self . request_context . push ( )
2023-11-15 22:23:08 +00:00
self . user = self . make_user ( ' user.com ' , cls = Web , has_hcard = True ,
has_redirects = True ,
2023-11-23 01:59:18 +00:00
obj_as1 = { * * ACTOR_AS1 , ' id ' : ' https://user.com/ ' } )
2023-07-07 04:16:04 +00:00
self . swentel_key = ndb . Key ( ActivityPub , ' https://mas.to/users/swentel ' )
2023-07-11 20:12:49 +00:00
self . masto_actor_key = ndb . Key ( ActivityPub , ' https://mas.to/actor ' )
2023-06-16 04:22:20 +00:00
2023-09-26 23:43:48 +00:00
for obj in ACTOR_BASE , ACTOR_FAKE :
obj [ ' publicKey ' ] [ ' publicKeyPem ' ] = self . user . public_pem ( ) . decode ( )
2023-06-02 19:55:07 +00:00
2023-06-15 22:09:03 +00:00
self . key_id_obj = Object ( id = ' http://my/key/id ' , as2 = {
* * ACTOR ,
' publicKey ' : {
' id ' : ' http://my/key/id#unused ' ,
' owner ' : ' http://own/er ' ,
' publicKeyPem ' : self . user . public_pem ( ) . decode ( ) ,
} ,
} )
self . key_id_obj . put ( )
2023-02-26 13:34:15 +00:00
2023-03-21 02:17:55 +00:00
def assert_object ( self , id , * * props ) :
2023-07-08 04:50:43 +00:00
props . setdefault ( ' delivered_protocol ' , ' web ' )
return super ( ) . assert_object ( id , * * props )
2023-03-21 02:17:55 +00:00
2023-09-27 20:55:16 +00:00
def sign ( self , path , body , host = None ) :
2023-02-26 13:34:15 +00:00
""" Constructs HTTP Signature, returns headers. """
digest = b64encode ( sha256 ( body . encode ( ) ) . digest ( ) ) . decode ( )
headers = {
' Date ' : ' Sun, 02 Jan 2022 03:04:05 GMT ' ,
2023-09-27 20:55:16 +00:00
' Host ' : host or ' localhost ' ,
2023-02-26 13:34:15 +00:00
' Content-Type ' : as2 . CONTENT_TYPE ,
' Digest ' : f ' SHA-256= { digest } ' ,
}
hs = HeaderSigner ( ' http://my/key/id#unused ' , self . user . private_pem ( ) . decode ( ) ,
algorithm = ' rsa-sha256 ' , sign_header = ' signature ' ,
headers = ( ' Date ' , ' Host ' , ' Digest ' , ' (request-target) ' ) )
return hs . sign ( headers , method = ' POST ' , path = path )
2023-09-27 20:55:16 +00:00
def post ( self , path , json = None , base_url = None , * * kwargs ) :
2023-02-26 13:34:15 +00:00
""" Wrapper around self.client.post that adds signature. """
body = json_dumps ( json )
2023-09-27 20:55:16 +00:00
host = domain_from_link ( base_url ) if base_url else None
headers = self . sign ( path , body , host = host )
return self . client . post ( path , data = body , headers = headers ,
base_url = base_url , * * kwargs )
2023-02-08 02:25:24 +00:00
2023-05-30 23:36:18 +00:00
def test_actor_fake ( self , * _ ) :
2023-09-26 23:43:48 +00:00
self . make_user ( ' fake:user ' , cls = Fake )
got = self . client . get ( ' /ap/fake:user ' , base_url = ' https://fa.brid.gy/ ' )
2023-05-30 23:36:18 +00:00
self . assertEqual ( 200 , got . status_code , got . get_data ( as_text = True ) )
2024-02-27 19:38:00 +00:00
self . assertEqual ( as2 . CONTENT_TYPE_LD_PROFILE , got . headers [ ' Content-Type ' ] )
2023-09-23 20:53:17 +00:00
self . assertEqual ( ACTOR_FAKE , got . json )
2023-05-30 23:36:18 +00:00
2023-09-26 23:43:48 +00:00
def test_actor_fake_protocol_subdomain ( self , * _ ) :
self . make_user ( ' fake:user ' , cls = Fake )
got = self . client . get ( ' /ap/fake:user ' , base_url = ' https://fa.brid.gy/ ' )
self . assertEqual ( 200 , got . status_code )
self . assertEqual ( ACTOR_FAKE , got . json )
2023-05-30 23:36:18 +00:00
def test_actor_web ( self , * _ ) :
""" Web users are special cased to drop the /web/ prefix. """
2023-03-19 22:43:55 +00:00
got = self . client . get ( ' /user.com ' )
2021-07-10 15:07:40 +00:00
self . assertEqual ( 200 , got . status_code )
2024-02-27 19:38:00 +00:00
self . assertEqual ( as2 . CONTENT_TYPE_LD_PROFILE , got . headers [ ' Content-Type ' ] )
2019-12-26 06:20:57 +00:00
self . assertEqual ( {
2023-06-02 19:55:07 +00:00
* * ACTOR_BASE ,
2024-01-14 21:53:28 +00:00
' type ' : ' Person ' ,
2017-10-13 06:16:32 +00:00
' name ' : ' Mrs. ☕ Foo ' ,
2024-01-14 21:53:28 +00:00
' summary ' : ' ' ,
2023-03-19 22:43:55 +00:00
' icon ' : { ' type ' : ' Image ' , ' url ' : ' https://user.com/me.jpg ' } ,
2023-06-16 04:22:20 +00:00
' image ' : { ' type ' : ' Image ' , ' url ' : ' https://user.com/me.jpg ' } ,
2021-07-10 15:07:40 +00:00
} , got . json )
2017-08-13 21:49:35 +00:00
2020-03-02 15:50:37 +00:00
def test_actor_blocked_tld ( self , _ , __ , ___ ) :
2021-08-18 14:59:52 +00:00
got = self . client . get ( ' /foo.json ' )
2021-07-10 15:07:40 +00:00
self . assertEqual ( 404 , got . status_code )
2020-03-02 15:50:37 +00:00
2023-06-02 19:55:07 +00:00
def test_actor_new_user_fetch ( self , _ , mock_get , __ ) :
2023-06-27 03:22:06 +00:00
self . user . obj_key . delete ( )
2023-06-02 19:55:07 +00:00
self . user . key . delete ( )
2023-06-27 03:22:06 +00:00
protocol . objects_cache . clear ( )
2023-06-02 19:55:07 +00:00
mock_get . return_value = requests_response ( test_web . ACTOR_HTML )
got = self . client . get ( ' /user.com ' )
self . assertEqual ( 200 , got . status_code )
2023-06-05 03:58:21 +00:00
self . assert_equals ( ACTOR_BASE_FULL , got . json , ignore = [ ' publicKeyPem ' ] )
2023-06-02 19:55:07 +00:00
def test_actor_new_user_fetch_no_mf2 ( self , _ , mock_get , __ ) :
2023-06-27 03:22:06 +00:00
self . user . obj_key . delete ( )
2023-06-02 19:55:07 +00:00
self . user . key . delete ( )
2023-06-27 03:22:06 +00:00
protocol . objects_cache . clear ( )
2023-06-02 19:55:07 +00:00
mock_get . return_value = requests_response ( ' <html></html> ' )
got = self . client . get ( ' /user.com ' )
self . assertEqual ( 200 , got . status_code )
2023-06-03 15:36:55 +00:00
self . assert_equals ( ACTOR_BASE , got . json , ignore = [ ' publicKeyPem ' ] )
2023-06-02 19:55:07 +00:00
2023-09-23 20:53:17 +00:00
def test_actor_new_user_fetch_fails ( self , _ , mock_get , ___ ) :
2023-06-02 19:55:07 +00:00
mock_get . side_effect = ReadTimeoutError ( None , None , None )
2023-02-08 02:25:24 +00:00
got = self . client . get ( ' /nope.com ' )
2023-06-02 19:55:07 +00:00
self . assertEqual ( 504 , got . status_code )
2023-02-08 02:25:24 +00:00
2023-09-23 20:53:17 +00:00
def test_actor_handle_existing_user ( self , _ , __ , ___ ) :
2023-12-01 00:31:41 +00:00
self . make_user ( ' fake:user ' , cls = Fake , obj_as1 = as2 . to_as1 ( {
* * ACTOR ,
' id ' : ' fake:user ' ,
} ) )
got = self . client . get ( ' /ap/fake:user ' , base_url = ' https://fa.brid.gy/ ' )
2023-09-23 20:53:17 +00:00
self . assertEqual ( 200 , got . status_code )
self . assert_equals ( {
* * ACTOR ,
* * ACTOR_FAKE ,
} , got . json , ignore = [ ' publicKeyPem ' ] )
def test_actor_handle_new_user ( self , _ , __ , ___ ) :
2023-11-03 22:11:21 +00:00
Fake . fetchable [ ' fake:user ' ] = as2 . to_as1 ( {
* * ACTOR ,
' id ' : ' fake:user ' ,
} )
2023-12-01 00:31:41 +00:00
got = self . client . get ( ' /ap/fake:user ' , base_url = ' https://fa.brid.gy/ ' )
2023-09-23 20:53:17 +00:00
self . assertEqual ( 200 , got . status_code )
self . assert_equals ( {
* * ACTOR ,
* * ACTOR_FAKE ,
2024-01-11 23:16:33 +00:00
' summary ' : ' [<a href= " https://fed.brid.gy/fa/fake:handle:user " >bridged</a> from <a href= " fake:user " >fake:handle:user</a> by <a href= " https://fed.brid.gy/ " >Bridgy Fed</a>] ' ,
' type ' : ' Application ' ,
2023-09-23 20:53:17 +00:00
} , got . json , ignore = [ ' publicKeyPem ' ] )
2024-02-29 20:48:35 +00:00
def test_actor_atproto_not_enabled ( self , * _ ) :
self . store_object ( id = ' did:plc:user ' , raw = { ' foo ' : ' baz ' } )
self . make_user ( ' did:plc:user ' , cls = ATProto )
2024-04-16 18:52:50 +00:00
got = self . client . get ( ' /ap/did:plc:user ' , base_url = ' https://bsky.brid.gy/ ' )
2024-04-17 23:49:06 +00:00
self . assertEqual ( 404 , got . status_code )
2024-02-29 20:48:35 +00:00
def test_actor_atproto_no_handle ( self , * _ ) :
2023-11-24 06:08:19 +00:00
self . store_object ( id = ' did:plc:user ' , raw = { ' foo ' : ' bar ' } )
2024-03-05 00:35:07 +00:00
self . store_object ( id = ' at://did:plc:user/app.bsky.actor.profile/self ' , bsky = {
' $type ' : ' app.bsky.actor.profile ' ,
' displayName ' : ' Alice ' ,
} )
2024-04-18 23:33:50 +00:00
self . make_user ( ' did:plc:user ' , cls = ATProto , enabled_protocols = [ ' activitypub ' ] )
2024-03-05 00:35:07 +00:00
2024-04-16 18:52:50 +00:00
got = self . client . get ( ' /ap/did:plc:user ' , base_url = ' https://bsky.brid.gy/ ' )
2023-11-24 06:08:19 +00:00
self . assertEqual ( 200 , got . status_code )
self . assertNotIn ( ' preferredUsername ' , got . json )
2023-09-23 20:53:17 +00:00
def test_actor_handle_user_fetch_fails ( self , _ , __ , ___ ) :
2023-12-01 00:31:41 +00:00
got = self . client . get ( ' /ap/fake/fake:nope ' )
2023-09-23 20:53:17 +00:00
self . assertEqual ( 404 , got . status_code )
2023-09-27 17:29:40 +00:00
def test_actor_no_matching_protocol ( self , * _ ) :
resp = self . client . get ( ' /foo.json ' ,
base_url = ' https://bridgy-federated.appspot.com/ ' )
self . assertEqual ( 404 , resp . status_code )
2023-11-03 01:03:20 +00:00
def test_actor_web_redirects ( self , * _ ) :
2023-12-01 00:31:41 +00:00
resp = self . client . get ( ' /ap/user.com ' )
self . assertEqual ( 301 , resp . status_code )
self . assertEqual ( ' https://fed.brid.gy/user.com ' , resp . headers [ ' Location ' ] )
self . user . ap_subdomain = ' web '
self . user . put ( )
resp = self . client . get ( ' /user.com ' , base_url = ' https://fed.brid.gy/ ' )
self . assertEqual ( 302 , resp . status_code )
self . assertEqual ( ' https://web.brid.gy/user.com ' , resp . headers [ ' Location ' ] )
self . user . ap_subdomain = ' fed '
self . user . put ( )
got = self . client . get ( ' /user.com ' , base_url = ' https://web.brid.gy/ ' )
self . assertEqual ( 302 , got . status_code )
self . assertEqual ( ' https://fed.brid.gy/user.com ' , got . headers [ ' Location ' ] )
2023-11-03 01:03:20 +00:00
2023-11-26 23:44:43 +00:00
def test_actor_opted_out ( self , * _ ) :
self . user . obj . our_as1 [ ' summary ' ] = ' #nobridge '
self . user . obj . put ( )
self . user . put ( )
got = self . client . get ( ' /user.com ' )
self . assertEqual ( 404 , got . status_code )
2024-01-06 21:59:31 +00:00
def test_instance_actor_fetch ( self , * _ ) :
def reset_instance_actor ( ) :
activitypub . _INSTANCE_ACTOR = testutil . global_user
self . addCleanup ( reset_instance_actor )
2024-04-21 23:33:26 +00:00
actor_as2 = json_loads ( util . read ( ' fed.brid.gy.as2.json ' ) )
2024-01-06 21:59:31 +00:00
self . make_user ( common . PRIMARY_DOMAIN , cls = Web , obj_as2 = actor_as2 )
activitypub . _INSTANCE_ACTOR = None
got = self . client . get ( f ' / { common . PRIMARY_DOMAIN } ' )
self . assertEqual ( 200 , got . status_code )
self . assert_equals ( {
* * actor_as2 ,
' id ' : ' http://localhost/fed.brid.gy ' ,
} , got . json , ignore = [ ' inbox ' , ' outbox ' , ' endpoints ' , ' followers ' ,
' following ' , ' publicKey ' , ' publicKeyPem ' ] )
2023-05-31 20:17:17 +00:00
def test_individual_inbox_no_user ( self , mock_head , mock_get , mock_post ) :
self . user . key . delete ( )
mock_get . side_effect = [ self . as2_resp ( LIKE_ACTOR ) ]
reply = {
* * REPLY ,
' actor ' : LIKE_ACTOR ,
}
2023-07-08 04:50:43 +00:00
self . _test_inbox_reply ( reply , mock_head , mock_get , mock_post )
2023-05-31 20:17:17 +00:00
2023-10-19 23:15:40 +00:00
self . assert_user ( ActivityPub , ' https://mas.to/actor ' , obj_as2 = LIKE_ACTOR )
2023-02-08 02:25:24 +00:00
2023-02-12 06:53:50 +00:00
def test_inbox_activity_without_id ( self , * _ ) :
note = copy . deepcopy ( NOTE )
del note [ ' id ' ]
2023-05-30 23:36:18 +00:00
resp = self . post ( ' /ap/sharedInbox ' , json = note )
2023-02-12 06:53:50 +00:00
self . assertEqual ( 400 , resp . status_code )
2023-07-08 04:50:43 +00:00
def test_inbox_reply_object ( self , mock_head , mock_get , mock_post ) :
self . _test_inbox_reply ( REPLY_OBJECT , mock_head , mock_get , mock_post )
2018-10-17 14:00:31 +00:00
2023-07-08 04:50:43 +00:00
self . assert_object ( ' http://mas.to/reply/id ' ,
source_protocol = ' activitypub ' ,
our_as1 = as2 . to_as1 ( REPLY_OBJECT ) ,
type = ' comment ' )
# auto-generated post activity
self . assert_object (
' http://mas.to/reply/id#bridgy-fed-create ' ,
source_protocol = ' activitypub ' ,
our_as1 = {
2023-11-25 22:50:08 +00:00
* * as2 . to_as1 ( {
* * REPLY ,
' actor ' : ACTOR ,
} ) ,
2023-07-08 04:50:43 +00:00
' id ' : ' http://mas.to/reply/id#bridgy-fed-create ' ,
' published ' : ' 2022-01-02T03:04:05+00:00 ' ,
} ,
status = ' complete ' ,
delivered = [ ' https://user.com/post ' ] ,
type = ' post ' ,
2023-07-17 15:31:28 +00:00
notify = [ self . user . key ] ,
2023-11-25 22:50:08 +00:00
users = [ self . swentel_key ] ,
2023-07-08 04:50:43 +00:00
)
def test_inbox_reply_object_wrapped ( self , mock_head , mock_get , mock_post ) :
self . _test_inbox_reply ( REPLY_OBJECT_WRAPPED , mock_head , mock_get , mock_post )
self . assert_object ( ' http://mas.to/reply/id ' ,
2023-03-03 23:12:51 +00:00
source_protocol = ' activitypub ' ,
2023-06-27 03:22:06 +00:00
our_as1 = as2 . to_as1 ( REPLY_OBJECT ) ,
2023-03-03 23:12:51 +00:00
type = ' comment ' )
2023-07-08 04:50:43 +00:00
# auto-generated post activity
self . assert_object (
' http://mas.to/reply/id#bridgy-fed-create ' ,
source_protocol = ' activitypub ' ,
our_as1 = {
2023-11-25 22:50:08 +00:00
* * as2 . to_as1 ( {
* * REPLY ,
' actor ' : ACTOR ,
} ) ,
2023-07-08 04:50:43 +00:00
' id ' : ' http://mas.to/reply/id#bridgy-fed-create ' ,
' published ' : ' 2022-01-02T03:04:05+00:00 ' ,
} ,
status = ' complete ' ,
delivered = [ ' https://user.com/post ' ] ,
type = ' post ' ,
2023-07-17 15:31:28 +00:00
notify = [ self . user . key ] ,
2023-11-25 22:50:08 +00:00
users = [ self . swentel_key ] ,
2023-07-08 04:50:43 +00:00
)
def test_inbox_reply_create_activity ( self , mock_head , mock_get , mock_post ) :
self . _test_inbox_reply ( REPLY , mock_head , mock_get , mock_post )
self . assert_object ( ' http://mas.to/reply/id ' ,
source_protocol = ' activitypub ' ,
2023-11-25 22:50:08 +00:00
our_as1 = as2 . to_as1 ( REPLY_OBJECT ) ,
2023-07-08 04:50:43 +00:00
type = ' comment ' )
# sent activity
self . assert_object (
' http://mas.to/reply/as2 ' ,
source_protocol = ' activitypub ' ,
as2 = REPLY ,
status = ' complete ' ,
delivered = [ ' https://user.com/post ' ] ,
type = ' post ' ,
2023-07-17 15:31:28 +00:00
notify = [ self . user . key ] ,
2023-11-25 22:50:08 +00:00
users = [ self . swentel_key ] ,
2023-07-08 04:50:43 +00:00
)
2018-10-15 15:09:36 +00:00
2023-07-08 04:50:43 +00:00
def _test_inbox_reply ( self , reply , mock_head , mock_get , mock_post ) :
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' https://user.com/post ' )
2023-05-31 20:17:17 +00:00
mock_get . side_effect = (
2023-11-25 22:50:08 +00:00
( list ( mock_get . side_effect ) if mock_get . side_effect
else [ self . as2_resp ( ACTOR ) ] )
2023-06-14 20:46:13 +00:00
+ [
requests_response ( test_web . NOTE_HTML ) ,
requests_response ( test_web . NOTE_HTML ) ,
WEBMENTION_DISCOVERY ,
] )
2017-08-24 14:41:46 +00:00
mock_post . return_value = requests_response ( )
2017-08-13 21:49:35 +00:00
2023-06-09 19:56:45 +00:00
got = self . post ( ' /ap/web/user.com/inbox ' , json = reply )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code , got . get_data ( as_text = True ) )
2023-03-19 22:43:55 +00:00
self . assert_req ( mock_get , ' https://user.com/post ' )
2023-07-08 04:50:43 +00:00
2023-11-07 04:17:23 +00:00
convert_id = reply [ ' id ' ]
2023-07-08 04:50:43 +00:00
if reply [ ' type ' ] != ' Create ' :
2023-07-24 21:31:07 +00:00
convert_id + = ' % 23bridgy-fed-create '
2023-07-08 04:50:43 +00:00
2022-03-17 04:11:09 +00:00
self . assert_req (
mock_post ,
2023-03-19 22:43:55 +00:00
' https://user.com/webmention ' ,
2022-03-17 04:11:09 +00:00
headers = { ' Accept ' : ' */* ' } ,
allow_redirects = False ,
2017-08-13 21:49:35 +00:00
data = {
2023-07-26 15:44:20 +00:00
' source ' : f ' https://ap.brid.gy/convert/web/ { convert_id } ' ,
2023-03-19 22:43:55 +00:00
' target ' : ' https://user.com/post ' ,
2017-08-13 21:49:35 +00:00
} ,
2022-03-17 04:11:09 +00:00
)
2017-10-04 14:07:22 +00:00
2023-11-25 22:50:08 +00:00
def test_inbox_reply_protocol_subdomain ( self , mock_head , mock_get , mock_post ) :
mock_get . return_value = self . as2_resp ( ACTOR )
2023-09-27 20:55:16 +00:00
Fake . fetchable [ ' fake:post ' ] = as2 . to_as1 ( {
* * NOTE_OBJECT ,
' id ' : ' fake:post ' ,
} )
reply = {
* * REPLY_OBJECT ,
2024-01-13 03:52:49 +00:00
' id ' : ' http://my/reply ' ,
2023-09-27 20:55:16 +00:00
' inReplyTo ' : ' fake:post ' ,
}
2023-11-25 22:50:08 +00:00
2023-09-27 20:55:16 +00:00
got = self . post ( ' /ap/fake:user/inbox ' , json = reply ,
base_url = ' https://fa.brid.gy/ ' )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2024-01-13 03:52:49 +00:00
self . assertEqual ( [ ( ' http://my/reply#bridgy-fed-create ' , ' fake:post:target ' ) ] ,
2023-10-31 19:49:15 +00:00
Fake . sent )
2023-09-27 20:55:16 +00:00
2023-11-23 01:59:18 +00:00
def test_inbox_reply_to_self_domain ( self , mock_head , mock_get , mock_post ) :
mock_get . return_value = test_web . ACTOR_HTML_RESP
self . _test_inbox_ignore_reply_to ( ' http://localhost/user.com ' ,
mock_head , mock_get , mock_post )
2022-11-14 15:07:33 +00:00
2023-11-11 22:52:21 +00:00
def test_inbox_reply_to_in_blocklist ( self , mock_head , mock_get , mock_post ) :
mock_get . return_value = HTML
self . _test_inbox_ignore_reply_to ( ' https://twitter.com/foo ' ,
mock_head , mock_get , mock_post )
2022-11-14 15:07:33 +00:00
2022-11-17 02:53:49 +00:00
def _test_inbox_ignore_reply_to ( self , reply_to , mock_head , mock_get , mock_post ) :
2019-04-16 14:42:10 +00:00
reply = copy . deepcopy ( REPLY_OBJECT )
2022-11-14 15:07:33 +00:00
reply [ ' inReplyTo ' ] = reply_to
2019-04-16 14:42:10 +00:00
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = reply )
2023-07-07 04:16:04 +00:00
self . assertEqual ( 204 , got . status_code , got . get_data ( as_text = True ) )
2019-04-16 14:42:10 +00:00
mock_post . assert_not_called ( )
2023-02-08 02:25:24 +00:00
def test_individual_inbox_create_obj ( self , * mocks ) :
2023-03-19 22:43:55 +00:00
self . _test_inbox_create_obj ( ' /user.com/inbox ' , * mocks )
2023-02-07 04:08:19 +00:00
def test_shared_inbox_create_obj ( self , * mocks ) :
self . _test_inbox_create_obj ( ' /inbox ' , * mocks )
2023-09-26 23:43:48 +00:00
def test_ap_sharedInbox_create_obj ( self , * mocks ) :
self . _test_inbox_create_obj ( ' /ap/sharedInbox ' , * mocks )
2023-02-07 04:08:19 +00:00
def _test_inbox_create_obj ( self , path , mock_head , mock_get , mock_post ) :
2023-07-08 04:50:43 +00:00
swentel = self . make_user ( ' https://mas.to/users/swentel ' , cls = ActivityPub )
Follower . get_or_create ( to = swentel , from_ = self . user )
2023-07-17 15:31:28 +00:00
bar = self . make_user ( ' fake:bar ' , cls = Fake , obj_id = ' fake:bar ' )
2024-01-13 03:52:49 +00:00
Follower . get_or_create ( to = self . make_user ( ' https://other/actor ' ,
2023-07-08 04:50:43 +00:00
cls = ActivityPub ) ,
2023-07-17 15:31:28 +00:00
from_ = bar )
baz = self . make_user ( ' fake:baz ' , cls = Fake , obj_id = ' fake:baz ' )
2023-07-08 04:50:43 +00:00
Follower . get_or_create ( to = swentel , from_ = baz )
2023-07-17 15:31:28 +00:00
baj = self . make_user ( ' fake:baj ' , cls = Fake , obj_id = ' fake:baj ' )
Follower . get_or_create ( to = swentel , from_ = baj , status = ' inactive ' )
2022-11-17 02:53:49 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' http://target ' )
2023-01-07 17:18:11 +00:00
mock_get . return_value = self . as2_resp ( ACTOR ) # source actor
2022-11-17 02:53:49 +00:00
mock_post . return_value = requests_response ( )
2023-02-26 13:34:15 +00:00
got = self . post ( path , json = NOTE )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code , got . get_data ( as_text = True ) )
2023-06-27 03:22:06 +00:00
2023-07-08 04:50:43 +00:00
expected_obj = {
* * as2 . to_as1 ( NOTE_OBJECT ) ,
' author ' : { ' id ' : ' https://masto.foo/@author ' } ,
2023-10-24 17:46:57 +00:00
' cc ' : [
{ ' id ' : ' https://mas.to/author/followers ' } ,
{ ' id ' : ' https://masto.foo/@other ' } ,
{ ' id ' : ' target ' } ,
] ,
2023-07-08 04:50:43 +00:00
}
2023-06-27 03:22:06 +00:00
self . assert_object ( NOTE_OBJECT [ ' id ' ] ,
source_protocol = ' activitypub ' ,
our_as1 = expected_obj ,
2023-07-17 15:31:28 +00:00
type = ' note ' ,
feed = [ self . user . key , baz . key ] )
2023-06-27 03:22:06 +00:00
2023-10-24 17:46:57 +00:00
expected_create = as2 . to_as1 ( common . unwrap ( NOTE ) )
2023-06-27 03:22:06 +00:00
expected_create . update ( {
' actor ' : as2 . to_as1 ( ACTOR ) ,
' object ' : expected_obj ,
2023-02-09 04:22:16 +00:00
} )
2023-03-19 22:43:55 +00:00
self . assert_object ( ' http://mas.to/note/as2 ' ,
2023-01-29 22:13:58 +00:00
source_protocol = ' activitypub ' ,
2023-06-27 03:22:06 +00:00
our_as1 = expected_create ,
2023-07-17 15:31:28 +00:00
users = [ ndb . Key ( ActivityPub , ' https://masto.foo/@author ' ) ] ,
2023-01-30 01:30:11 +00:00
type = ' post ' ,
2023-07-08 04:50:43 +00:00
object_ids = [ NOTE_OBJECT [ ' id ' ] ] ,
status = ' complete ' ,
delivered = [ ' shared:target ' ] ,
delivered_protocol = ' fake ' )
2022-11-17 02:53:49 +00:00
2023-03-19 23:21:44 +00:00
def test_repost_of_indieweb ( self , mock_head , mock_get , mock_post ) :
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' https://user.com/orig ' )
2023-03-10 03:56:04 +00:00
mock_get . return_value = WEBMENTION_DISCOVERY
2023-02-22 03:19:56 +00:00
mock_post . return_value = requests_response ( ) # webmention
2023-03-19 22:43:55 +00:00
orig_url = ' https://user.com/orig '
2023-02-22 03:19:56 +00:00
note = {
* * NOTE_OBJECT ,
2023-03-19 23:21:44 +00:00
' id ' : ' https://user.com/orig ' ,
2023-02-22 03:19:56 +00:00
}
2023-03-19 23:21:44 +00:00
del note [ ' url ' ]
2023-06-15 22:09:03 +00:00
Object ( id = orig_url , mf2 = microformats2 . object_to_json ( as2 . to_as1 ( note ) ) ,
source_protocol = ' web ' ) . put ( )
2023-02-22 03:19:56 +00:00
2023-03-19 23:21:44 +00:00
repost = copy . deepcopy ( REPOST_FULL )
repost [ ' object ' ] = f ' http://localhost/r/ { orig_url } '
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = repost )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code , got . get_data ( as_text = True ) )
2023-02-22 03:19:56 +00:00
self . assert_req (
mock_post ,
2023-03-19 22:43:55 +00:00
' https://user.com/webmention ' ,
2023-02-22 03:19:56 +00:00
headers = { ' Accept ' : ' */* ' } ,
allow_redirects = False ,
data = {
2023-11-07 04:17:23 +00:00
' source ' : f ' https://ap.brid.gy/convert/web/ { REPOST [ " id " ] } ' ,
2023-02-22 03:19:56 +00:00
' target ' : orig_url ,
} ,
)
self . assert_object ( REPOST_FULL [ ' id ' ] ,
source_protocol = ' activitypub ' ,
status = ' complete ' ,
2023-07-07 04:16:04 +00:00
as2 = {
* * REPOST ,
' actor ' : ACTOR ,
' object ' : orig_url ,
} ,
users = [ self . swentel_key ] ,
2023-03-19 22:43:55 +00:00
delivered = [ ' https://user.com/orig ' ] ,
2023-02-22 03:19:56 +00:00
type = ' share ' ,
2023-03-19 23:21:44 +00:00
object_ids = [ ' https://user.com/orig ' ] )
2023-02-22 03:19:56 +00:00
2023-03-19 23:21:44 +00:00
def test_shared_inbox_repost_of_fediverse ( self , mock_head , mock_get , mock_post ) :
2023-10-19 22:01:19 +00:00
to = self . make_user ( ACTOR [ ' id ' ] , cls = ActivityPub )
Follower . get_or_create ( to = to , from_ = self . user )
2023-07-17 15:31:28 +00:00
baz = self . make_user ( ' fake:baz ' , cls = Fake , obj_id = ' fake:baz ' )
2023-10-19 22:01:19 +00:00
Follower . get_or_create ( to = to , from_ = baz )
2023-07-17 15:31:28 +00:00
baj = self . make_user ( ' fake:baj ' , cls = Fake , obj_id = ' fake:baj ' )
2023-10-19 22:01:19 +00:00
Follower . get_or_create ( to = to , from_ = baj , status = ' inactive ' )
2023-02-08 18:48:05 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' http://target ' )
2024-02-10 00:47:49 +00:00
mock_get . return_value = self . as2_resp ( NOTE_OBJECT )
2023-02-08 18:48:05 +00:00
2023-05-30 23:36:18 +00:00
got = self . post ( ' /ap/sharedInbox ' , json = REPOST )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code , got . get_data ( as_text = True ) )
2023-02-08 18:48:05 +00:00
2023-03-19 23:21:44 +00:00
mock_post . assert_not_called ( ) # no webmention
2023-02-08 18:48:05 +00:00
self . assert_object ( REPOST [ ' id ' ] ,
source_protocol = ' activitypub ' ,
2023-07-17 15:31:28 +00:00
status = ' complete ' ,
2024-01-13 03:52:49 +00:00
as2 = REPOST ,
2023-07-17 15:31:28 +00:00
users = [ self . swentel_key ] ,
feed = [ self . user . key , baz . key ] ,
delivered = [ ' shared:target ' ] ,
delivered_protocol = ' fake ' ,
2023-02-08 18:48:05 +00:00
type = ' share ' ,
2023-03-19 23:21:44 +00:00
object_ids = [ REPOST [ ' object ' ] ] )
def test_inbox_no_user ( self , mock_head , mock_get , mock_post ) :
mock_get . side_effect = [
# source actor
self . as2_resp ( LIKE_WITH_ACTOR [ ' actor ' ] ) ,
2023-06-14 20:46:13 +00:00
# protocol inference
requests_response ( test_web . NOTE_HTML ) ,
requests_response ( test_web . NOTE_HTML ) ,
2023-03-19 23:21:44 +00:00
# target post webmention discovery
HTML ,
]
2023-07-07 04:16:04 +00:00
got = self . post ( ' /ap/sharedInbox ' , json = {
* * LIKE ,
' object ' : ' http://nope.com/post ' ,
} )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2023-03-19 23:21:44 +00:00
self . assert_object ( ' http://mas.to/like#ok ' ,
2023-06-09 19:56:45 +00:00
# no nope.com Web user key since it didn't exist
2023-03-19 23:21:44 +00:00
source_protocol = ' activitypub ' ,
2023-06-09 19:56:45 +00:00
status = ' ignored ' ,
2023-06-27 03:22:06 +00:00
our_as1 = as2 . to_as1 ( {
* * LIKE_WITH_ACTOR ,
' object ' : ' http://nope.com/post ' ,
} ) ,
2023-03-19 23:21:44 +00:00
type = ' like ' ,
2023-07-17 15:31:28 +00:00
notify = [ self . user . key ] ,
users = [ self . masto_actor_key ] ,
2023-03-19 23:21:44 +00:00
object_ids = [ ' http://nope.com/post ' ] )
2023-02-08 18:48:05 +00:00
2024-02-27 20:05:18 +00:00
def test_inbox_private ( self , * mocks ) :
self . _test_inbox_with_to_ignored ( [ ] , * mocks )
def test_inbox_unlisted ( self , * mocks ) :
self . _test_inbox_with_to_ignored ( [ ' @unlisted ' ] , * mocks )
2024-04-21 15:08:05 +00:00
def test_inbox_dm ( self , * mocks ) :
self . _test_inbox_with_to_ignored ( [ ' http://localhost/web/user.com ' ] , * mocks )
2024-02-27 20:05:18 +00:00
def _test_inbox_with_to_ignored ( self , to , mock_head , mock_get , mock_post ) :
2023-10-19 22:01:19 +00:00
Follower . get_or_create ( to = self . make_user ( ACTOR [ ' id ' ] , cls = ActivityPub ) ,
2023-06-07 21:24:00 +00:00
from_ = self . user )
2022-08-24 00:37:50 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' http://target ' )
2023-01-07 17:18:11 +00:00
mock_get . return_value = self . as2_resp ( ACTOR ) # source actor
2022-08-24 00:37:50 +00:00
not_public = copy . deepcopy ( NOTE )
2024-02-27 20:05:18 +00:00
not_public [ ' object ' ] [ ' to ' ] = to
2022-08-24 00:37:50 +00:00
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = not_public )
2023-02-09 04:22:16 +00:00
self . assertEqual ( 200 , got . status_code , got . get_data ( as_text = True ) )
2023-03-08 21:10:41 +00:00
self . assertIsNone ( Object . get_by_id ( not_public [ ' id ' ] ) )
2023-02-12 06:53:50 +00:00
self . assertIsNone ( Object . get_by_id ( not_public [ ' object ' ] [ ' id ' ] ) )
2022-08-24 00:37:50 +00:00
2024-04-21 15:36:03 +00:00
def test_inbox_dm_yes_to_bot_user_enables_protocol ( self , * mocks ) :
user = self . make_user ( ACTOR [ ' id ' ] , cls = ActivityPub )
self . assertFalse ( ActivityPub . is_enabled_to ( ExplicitEnableFake , user ) )
got = self . post ( ' /ap/sharedInbox ' , json = {
' type ' : ' Create ' ,
' id ' : ' https://mas.to/dm#create ' ,
' to ' : [ ' https://eefake.brid.gy/eefake.brid.gy ' ] ,
' object ' : {
' type ' : ' Note ' ,
' id ' : ' https://mas.to/dm ' ,
' attributedTo ' : ACTOR [ ' id ' ] ,
' to ' : [ ' https://eefake.brid.gy/eefake.brid.gy ' ] ,
' content ' : ' yes ' ,
} ,
} )
self . assertEqual ( 200 , got . status_code , got . get_data ( as_text = True ) )
user = user . key . get ( )
self . assertTrue ( ActivityPub . is_enabled_to ( ExplicitEnableFake , user ) )
2024-02-27 06:52:52 +00:00
def test_inbox_actor_blocklisted ( self , mock_head , mock_get , mock_post ) :
got = self . post ( ' /ap/sharedInbox ' , json = {
' type ' : ' Delete ' ,
' id ' : ' http://inst/foo#delete ' ,
' actor ' : ' http://localhost:3000/foo ' ,
' object ' : ' http://inst/foo ' ,
} )
self . assertEqual ( 400 , got . status_code , got . get_data ( as_text = True ) )
self . assertIsNone ( Object . get_by_id ( ' http://localhost:3000/foo ' ) )
self . assertIsNone ( Object . get_by_id ( ' http://inst/foo#delete ' ) )
self . assertIsNone ( Object . get_by_id ( ' http://inst/foo ' ) )
2018-10-23 14:11:44 +00:00
def test_inbox_like ( self , mock_head , mock_get , mock_post ) :
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' https://user.com/post ' )
2017-10-13 06:14:46 +00:00
mock_get . side_effect = [
# source actor
2023-01-07 17:18:11 +00:00
self . as2_resp ( LIKE_WITH_ACTOR [ ' actor ' ] ) ,
2023-06-14 20:46:13 +00:00
requests_response ( test_web . NOTE_HTML ) ,
requests_response ( test_web . NOTE_HTML ) ,
2023-02-19 01:53:27 +00:00
WEBMENTION_DISCOVERY ,
2017-10-13 06:14:46 +00:00
]
2017-10-11 05:42:19 +00:00
mock_post . return_value = requests_response ( )
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = LIKE )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2017-10-13 06:14:46 +00:00
2023-07-11 20:12:49 +00:00
self . assertIn ( self . as2_req ( ' https://mas.to/actor ' ) , mock_get . mock_calls )
2023-06-14 20:46:13 +00:00
self . assertIn ( self . req ( ' https://user.com/post ' ) , mock_get . mock_calls )
2017-10-10 00:29:50 +00:00
2017-10-11 05:42:19 +00:00
args , kwargs = mock_post . call_args
2023-03-19 22:43:55 +00:00
self . assertEqual ( ( ' https://user.com/webmention ' , ) , args )
2019-12-26 06:20:57 +00:00
self . assertEqual ( {
2023-11-07 04:17:23 +00:00
' source ' : ' https://ap.brid.gy/convert/web/http://mas.to/like %23o k ' ,
2023-03-19 22:43:55 +00:00
' target ' : ' https://user.com/post ' ,
2017-10-11 05:42:19 +00:00
} , kwargs [ ' data ' ] )
2023-03-19 22:43:55 +00:00
self . assert_object ( ' http://mas.to/like#ok ' ,
2023-07-17 15:31:28 +00:00
notify = [ self . user . key ] ,
users = [ self . masto_actor_key ] ,
2023-01-29 22:13:58 +00:00
source_protocol = ' activitypub ' ,
status = ' complete ' ,
2023-06-27 03:22:06 +00:00
our_as1 = as2 . to_as1 ( LIKE_WITH_ACTOR ) ,
2023-03-19 22:43:55 +00:00
delivered = [ ' https://user.com/post ' ] ,
2023-01-30 01:30:11 +00:00
type = ' like ' ,
object_ids = [ LIKE [ ' object ' ] ] )
2017-10-17 05:21:13 +00:00
2023-05-31 20:17:17 +00:00
def test_inbox_like_indirect_user_creates_User ( self , mock_get , * _ ) :
self . user . direct = False
self . user . put ( )
mock_get . return_value = self . as2_resp ( LIKE_ACTOR )
self . test_inbox_like ( )
2023-10-19 23:15:40 +00:00
self . assert_user ( ActivityPub , ' https://mas.to/actor ' , obj_as2 = LIKE_ACTOR )
2023-05-31 20:17:17 +00:00
2023-09-27 16:42:40 +00:00
def test_inbox_like_no_object_error ( self , * _ ) :
Fake . fetchable = { ' fake:user ' : { ' id ' : ' fake:user ' } }
got = self . post ( ' /inbox ' , json = {
' id ' : ' fake:like ' ,
' type ' : ' Like ' ,
' actor ' : ' fake:user ' ,
' object ' : None ,
} )
self . assertEqual ( 400 , got . status_code )
2023-02-12 06:23:01 +00:00
def test_inbox_follow_accept_with_id ( self , * mocks ) :
2023-10-31 19:49:15 +00:00
self . _test_inbox_follow_accept ( FOLLOW_WRAPPED , ACCEPT , * mocks )
2023-01-09 01:28:12 +00:00
2023-03-03 17:24:59 +00:00
follow = {
* * FOLLOW_WITH_ACTOR ,
2023-10-24 17:46:57 +00:00
' url ' : ' https://mas.to/users/swentel#followed-user.com ' ,
2023-03-03 17:24:59 +00:00
}
2023-03-19 22:43:55 +00:00
self . assert_object ( ' https://mas.to/6d1a ' ,
2023-07-17 15:31:28 +00:00
users = [ self . swentel_key ] ,
notify = [ self . user . key ] ,
2023-01-29 22:13:58 +00:00
source_protocol = ' activitypub ' ,
status = ' complete ' ,
2023-06-27 03:22:06 +00:00
our_as1 = as2 . to_as1 ( follow ) ,
2023-03-19 22:43:55 +00:00
delivered = [ ' https://user.com/ ' ] ,
2023-01-30 01:30:11 +00:00
type = ' follow ' ,
object_ids = [ FOLLOW [ ' object ' ] ] )
2023-01-09 01:28:12 +00:00
2023-02-12 06:23:01 +00:00
def test_inbox_follow_accept_with_object ( self , * mocks ) :
2023-02-19 01:53:27 +00:00
follow = {
2023-03-03 17:24:59 +00:00
* * FOLLOW ,
2023-06-25 19:54:44 +00:00
' object ' : {
' id ' : FOLLOW [ ' object ' ] ,
' url ' : FOLLOW [ ' object ' ] ,
} ,
2023-02-19 01:53:27 +00:00
}
2023-10-24 17:46:57 +00:00
accept = copy . deepcopy ( ACCEPT )
accept [ ' object ' ] [ ' url ' ] = ' https://mas.to/users/swentel#followed-https://user.com/ '
2023-10-31 19:49:15 +00:00
self . _test_inbox_follow_accept ( follow , accept , * mocks )
2023-01-09 01:28:12 +00:00
2023-01-12 20:28:34 +00:00
follow . update ( {
2023-03-03 17:24:59 +00:00
' actor ' : ACTOR ,
2023-03-19 22:43:55 +00:00
' url ' : ' https://mas.to/users/swentel#followed-https://user.com/ ' ,
2023-01-12 20:28:34 +00:00
} )
2023-03-19 22:43:55 +00:00
self . assert_object ( ' https://mas.to/6d1a ' ,
2023-07-17 15:31:28 +00:00
users = [ self . swentel_key ] ,
notify = [ self . user . key ] ,
2023-01-29 22:13:58 +00:00
source_protocol = ' activitypub ' ,
status = ' complete ' ,
2023-06-27 03:22:06 +00:00
our_as1 = as2 . to_as1 ( follow ) ,
2023-03-19 22:43:55 +00:00
delivered = [ ' https://user.com/ ' ] ,
2023-01-30 01:30:11 +00:00
type = ' follow ' ,
object_ids = [ FOLLOW [ ' object ' ] ] )
2023-01-12 20:28:34 +00:00
2023-11-23 01:59:18 +00:00
def test_inbox_follow_accept_shared_inbox ( self , mock_head , mock_get , mock_post ) :
self . _test_inbox_follow_accept ( FOLLOW_WRAPPED , ACCEPT ,
mock_head , mock_get , mock_post ,
2023-08-09 18:26:36 +00:00
inbox_path = ' /ap/sharedInbox ' )
2023-10-24 17:46:57 +00:00
url = ' https://mas.to/users/swentel#followed-user.com '
2023-08-09 18:26:36 +00:00
self . assert_object ( ' https://mas.to/6d1a ' ,
users = [ self . swentel_key ] ,
notify = [ self . user . key ] ,
source_protocol = ' activitypub ' ,
status = ' complete ' ,
our_as1 = as2 . to_as1 ( { * * FOLLOW_WITH_ACTOR , ' url ' : url } ) ,
delivered = [ ' https://user.com/ ' ] ,
type = ' follow ' ,
object_ids = [ FOLLOW [ ' object ' ] ] )
2023-07-07 04:16:04 +00:00
def test_inbox_follow_accept_webmention_fails ( self , mock_head , mock_get ,
mock_post ) :
2023-06-04 22:11:52 +00:00
mock_post . side_effect = [
requests_response ( ) , # AP Accept
requests . ConnectionError ( ) , # webmention
]
2023-10-31 19:49:15 +00:00
self . _test_inbox_follow_accept ( FOLLOW_WRAPPED , ACCEPT ,
2023-06-04 22:11:52 +00:00
mock_head , mock_get , mock_post )
2023-10-24 17:46:57 +00:00
url = ' https://mas.to/users/swentel#followed-user.com '
2023-06-04 22:11:52 +00:00
self . assert_object ( ' https://mas.to/6d1a ' ,
2023-07-17 15:31:28 +00:00
users = [ self . swentel_key ] ,
notify = [ self . user . key ] ,
2023-06-04 22:11:52 +00:00
source_protocol = ' activitypub ' ,
2023-07-07 04:16:04 +00:00
status = ' failed ' ,
our_as1 = as2 . to_as1 ( { * * FOLLOW_WITH_ACTOR , ' url ' : url } ) ,
2023-06-04 22:11:52 +00:00
delivered = [ ] ,
2023-07-07 04:16:04 +00:00
failed = [ ' https://user.com/ ' ] ,
2023-06-04 22:11:52 +00:00
type = ' follow ' ,
object_ids = [ FOLLOW [ ' object ' ] ] )
2023-10-31 19:49:15 +00:00
def _test_inbox_follow_accept ( self , follow_as2 , accept_as2 , mock_head ,
mock_get , mock_post , inbox_path = ' /user.com/inbox ' ) :
2023-06-09 19:28:07 +00:00
# this should makes us make the follower ActivityPub as direct=True
self . user . direct = False
self . user . put ( )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' https://user.com/ ' )
2018-10-21 22:28:42 +00:00
mock_get . side_effect = [
2023-11-23 01:59:18 +00:00
self . as2_resp ( ACTOR ) , # source actor
2023-02-19 01:53:27 +00:00
WEBMENTION_DISCOVERY ,
2018-10-21 22:28:42 +00:00
]
2023-06-04 22:11:52 +00:00
if not mock_post . return_value and not mock_post . side_effect :
mock_post . return_value = requests_response ( )
2018-10-21 22:28:42 +00:00
2023-08-09 18:26:36 +00:00
got = self . post ( inbox_path , json = follow_as2 )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2018-10-21 22:28:42 +00:00
2022-11-24 16:20:04 +00:00
mock_get . assert_has_calls ( (
self . as2_req ( FOLLOW [ ' actor ' ] ) ,
) )
2018-10-21 22:28:42 +00:00
2018-10-23 14:11:44 +00:00
# check AP Accept
self . assertEqual ( 2 , len ( mock_post . call_args_list ) )
args , kwargs = mock_post . call_args_list [ 0 ]
2023-03-19 22:43:55 +00:00
self . assertEqual ( ( ' http://mas.to/inbox ' , ) , args )
2023-01-09 01:28:12 +00:00
self . assertEqual ( accept_as2 , json_loads ( kwargs [ ' data ' ] ) )
2018-10-21 22:28:42 +00:00
2018-10-23 14:11:44 +00:00
# check webmention
args , kwargs = mock_post . call_args_list [ 1 ]
2023-03-19 22:43:55 +00:00
self . assertEqual ( ( ' https://user.com/webmention ' , ) , args )
2019-12-26 06:20:57 +00:00
self . assertEqual ( {
2023-11-07 04:17:23 +00:00
' source ' : ' https://ap.brid.gy/convert/web/https://mas.to/6d1a ' ,
2023-03-19 22:43:55 +00:00
' target ' : ' https://user.com/ ' ,
2018-10-23 14:11:44 +00:00
} , kwargs [ ' data ' ] )
2023-06-09 19:28:07 +00:00
# check that we stored Follower and ActivityPub user for the follower
2023-06-07 21:24:00 +00:00
self . assert_entities_equal (
Follower ( to = self . user . key ,
from_ = ActivityPub ( id = ACTOR [ ' id ' ] ) . key ,
status = ' active ' ,
follow = Object ( id = FOLLOW [ ' id ' ] ) . key ) ,
Follower . query ( ) . fetch ( ) ,
ignore = [ ' created ' , ' updated ' ] )
2018-10-22 00:37:33 +00:00
2023-06-27 03:22:06 +00:00
self . assert_user ( ActivityPub , ' https://mas.to/users/swentel ' ,
2024-04-19 19:25:27 +00:00
obj_as2 = ACTOR , direct = False )
2023-06-27 03:22:06 +00:00
self . assert_user ( Web , ' user.com ' , direct = False ,
has_hcard = True , has_redirects = True )
2023-06-09 19:28:07 +00:00
2022-12-06 22:09:44 +00:00
def test_inbox_follow_use_instead_strip_www ( self , mock_head , mock_get , mock_post ) :
2023-11-15 22:23:08 +00:00
self . make_user ( ' www.user.com ' , cls = Web , use_instead = self . user . key )
2022-12-06 22:09:44 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' https://www.user.com/ ' )
2022-12-06 22:09:44 +00:00
mock_get . side_effect = [
# source actor
2023-01-07 17:18:11 +00:00
self . as2_resp ( ACTOR ) ,
2023-11-23 01:59:18 +00:00
# target user
test_web . ACTOR_HTML_RESP ,
2022-12-06 22:09:44 +00:00
# target post webmention discovery
requests_response ( ' <html></html> ' ) ,
]
mock_post . return_value = requests_response ( )
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = FOLLOW_WRAPPED )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2022-12-06 22:09:44 +00:00
2023-06-07 21:24:00 +00:00
follower = Follower . query ( ) . get ( )
self . assert_entities_equal (
Follower ( to = self . user . key ,
from_ = ActivityPub ( id = ACTOR [ ' id ' ] ) . key ,
status = ' active ' ,
follow = Object ( id = FOLLOW [ ' id ' ] ) . key ) ,
follower ,
ignore = [ ' created ' , ' updated ' ] )
# double check that Follower doesn't have www
self . assertEqual ( ' user.com ' , follower . to . id ( ) )
# double check that follow Object doesn't have www
2022-12-06 22:09:44 +00:00
self . assertEqual ( ' active ' , follower . status )
2023-10-24 17:46:57 +00:00
self . assertEqual ( ' https://mas.to/users/swentel#followed-user.com ' ,
2023-06-07 21:24:00 +00:00
follower . follow . get ( ) . as2 [ ' url ' ] )
2023-12-24 18:04:01 +00:00
def test_inbox_follow_web_brid_gy_subdomain ( self , mock_head , mock_get , mock_post ) :
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' https://user.com/ ' )
2023-12-24 18:04:01 +00:00
mock_get . side_effect = [
# source actor
self . as2_resp ( ACTOR ) ,
# target user
test_web . ACTOR_HTML_RESP ,
# target post webmention discovery
requests_response ( ' <html></html> ' ) ,
]
mock_post . return_value = requests_response ( )
got = self . post ( ' /user.com/inbox ' , base_url = ' https://web.brid.gy/ ' , json = {
* * FOLLOW_WRAPPED ,
' object ' : ' https://web.brid.gy/user.com ' ,
} )
self . assertEqual ( 202 , got . status_code )
# check that AP Accept uses web.brid.gy, not fed.brid.gy
args , kwargs = mock_post . call_args_list [ 0 ]
self . assert_equals ( ( ' http://mas.to/inbox ' , ) , args )
self . assert_equals ( {
' type ' : ' Accept ' ,
2024-04-19 20:16:48 +00:00
' id ' : ' https://web.brid.gy/r/user.com/followers#accept-https://mas.to/6d1a ' ,
2023-12-24 18:04:01 +00:00
' actor ' : ' https://web.brid.gy/user.com ' ,
' object ' : {
' type ' : ' Follow ' ,
' id ' : ' https://mas.to/6d1a ' ,
' object ' : ' https://web.brid.gy/user.com ' ,
' actor ' : ' https://mas.to/users/swentel ' ,
' url ' : ' https://mas.to/users/swentel#followed-user.com ' ,
} ,
} , json_loads ( kwargs [ ' data ' ] ) , ignore = [ ' to ' , ' @context ' ] )
2019-08-01 14:32:45 +00:00
def test_inbox_undo_follow ( self , mock_head , mock_get , mock_post ) :
2023-06-14 20:46:13 +00:00
follower = Follower ( to = self . user . key ,
2023-10-19 22:01:19 +00:00
from_ = ActivityPub ( id = ACTOR [ ' id ' ] ) . key ,
2023-06-14 20:46:13 +00:00
status = ' active ' )
follower . put ( )
2019-08-01 14:32:45 +00:00
2023-07-17 15:31:28 +00:00
mock_get . side_effect = [
self . as2_resp ( ACTOR ) ,
2023-11-23 01:59:18 +00:00
test_web . ACTOR_HTML_RESP ,
2023-07-17 15:31:28 +00:00
WEBMENTION_DISCOVERY ,
]
mock_post . return_value = requests_response ( )
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = UNDO_FOLLOW_WRAPPED )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2019-08-01 14:32:45 +00:00
2023-06-07 21:24:00 +00:00
# check that the Follower is now inactive
self . assertEqual ( ' inactive ' , follower . key . get ( ) . status )
2019-08-01 14:32:45 +00:00
2022-11-30 06:43:04 +00:00
def test_inbox_follow_inactive ( self , mock_head , mock_get , mock_post ) :
2023-10-19 22:01:19 +00:00
follower = Follower . get_or_create (
to = self . user ,
2024-01-13 03:52:49 +00:00
from_ = self . make_user ( ACTOR [ ' id ' ] , cls = ActivityPub , obj_as2 = ACTOR ) ,
2023-10-19 22:01:19 +00:00
status = ' inactive ' )
2022-11-30 06:43:04 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' https://user.com/ ' )
2022-11-30 06:43:04 +00:00
mock_get . side_effect = [
2023-11-23 01:59:18 +00:00
test_web . ACTOR_HTML_RESP ,
2023-02-19 01:53:27 +00:00
WEBMENTION_DISCOVERY ,
2022-11-30 06:43:04 +00:00
]
mock_post . return_value = requests_response ( )
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = FOLLOW_WRAPPED )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2022-11-30 06:43:04 +00:00
# check that the Follower is now active
2023-06-07 21:24:00 +00:00
self . assertEqual ( ' active ' , follower . key . get ( ) . status )
2022-11-30 06:43:04 +00:00
2019-08-01 14:32:45 +00:00
def test_inbox_undo_follow_doesnt_exist ( self , mock_head , mock_get , mock_post ) :
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' https://user.com/ ' )
2023-02-15 18:57:11 +00:00
mock_get . side_effect = [
self . as2_resp ( ACTOR ) ,
2023-11-23 01:59:18 +00:00
test_web . ACTOR_HTML_RESP ,
2023-07-17 15:31:28 +00:00
WEBMENTION_DISCOVERY ,
2023-02-15 18:57:11 +00:00
]
2023-07-17 15:31:28 +00:00
mock_post . return_value = requests_response ( )
2019-08-01 14:32:45 +00:00
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = UNDO_FOLLOW_WRAPPED )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2019-08-01 14:32:45 +00:00
def test_inbox_undo_follow_inactive ( self , mock_head , mock_get , mock_post ) :
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' https://user.com/ ' )
2023-02-15 18:57:11 +00:00
mock_get . side_effect = [
self . as2_resp ( ACTOR ) ,
2023-11-23 01:59:18 +00:00
test_web . ACTOR_HTML_RESP ,
2023-07-17 15:31:28 +00:00
WEBMENTION_DISCOVERY ,
2023-02-15 18:57:11 +00:00
]
2023-07-17 15:31:28 +00:00
mock_post . return_value = requests_response ( )
2023-02-15 18:57:11 +00:00
2023-06-07 21:24:00 +00:00
follower = Follower . get_or_create ( to = self . user ,
from_ = ActivityPub . get_or_create ( ACTOR [ ' id ' ] ) ,
status = ' inactive ' )
2019-08-01 14:32:45 +00:00
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = UNDO_FOLLOW_WRAPPED )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2023-06-07 21:24:00 +00:00
self . assertEqual ( ' inactive ' , follower . key . get ( ) . status )
2019-08-01 14:32:45 +00:00
2023-01-10 06:58:30 +00:00
def test_inbox_undo_follow_composite_object ( self , mock_head , mock_get , mock_post ) :
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
mock_head . return_value = requests_response ( url = ' https://user.com/ ' )
2023-02-15 18:57:11 +00:00
mock_get . side_effect = [
self . as2_resp ( ACTOR ) ,
2023-11-23 01:59:18 +00:00
test_web . ACTOR_HTML_RESP ,
2023-07-17 15:31:28 +00:00
WEBMENTION_DISCOVERY ,
2023-02-15 18:57:11 +00:00
]
2023-07-17 15:31:28 +00:00
mock_post . return_value = requests_response ( )
2023-02-15 18:57:11 +00:00
2024-04-17 23:43:10 +00:00
follower_key = ActivityPub . get_or_create ( ACTOR [ ' id ' ] )
follower = Follower . get_or_create ( to = self . user , from_ = follower_key ,
2023-06-07 21:24:00 +00:00
status = ' inactive ' )
2023-01-10 06:58:30 +00:00
undo_follow = copy . deepcopy ( UNDO_FOLLOW_WRAPPED )
undo_follow [ ' object ' ] [ ' object ' ] = { ' id ' : undo_follow [ ' object ' ] [ ' object ' ] }
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = undo_follow )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2023-06-07 21:24:00 +00:00
self . assertEqual ( ' inactive ' , follower . key . get ( ) . status )
2023-01-10 06:58:30 +00:00
2023-11-26 04:07:14 +00:00
def test_inbox_unsupported_type ( self , mock_head , mock_get , mock_post ) :
mock_get . return_value = self . as2_resp ( ACTOR )
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = {
2017-10-17 05:21:13 +00:00
' @context ' : [ ' https://www.w3.org/ns/activitystreams ' ] ,
' id ' : ' https://xoxo.zone/users/aaronpk#follows/40 ' ,
2024-04-18 23:03:35 +00:00
' type ' : ' Arrive ' ,
2017-10-17 05:21:13 +00:00
' actor ' : ' https://xoxo.zone/users/aaronpk ' ,
2024-04-18 23:03:35 +00:00
' object ' : ' http://a/place ' ,
2021-07-10 15:07:40 +00:00
} )
self . assertEqual ( 501 , got . status_code )
2020-03-01 04:28:53 +00:00
2022-12-10 17:01:04 +00:00
def test_inbox_bad_object_url ( self , mock_head , mock_get , mock_post ) :
# https://console.cloud.google.com/errors/detail/CMKn7tqbq-GIRA;time=P30D?project=bridgy-federated
2023-01-07 17:18:11 +00:00
mock_get . return_value = self . as2_resp ( ACTOR ) # source actor
2023-02-12 06:53:50 +00:00
2023-03-19 22:43:55 +00:00
id = ' https://mas.to/users/tmichellemoore#likes/56486252 '
2023-02-12 06:53:50 +00:00
bad_url = ' http://localhost/r/Testing \u2013 Brid.gy \u2013 Post to Mastodon 3 '
2023-07-07 04:16:04 +00:00
bad = {
2022-12-10 17:01:04 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-02-12 06:53:50 +00:00
' id ' : id ,
2022-12-10 17:01:04 +00:00
' type ' : ' Like ' ,
' actor ' : ACTOR [ ' id ' ] ,
2023-02-12 06:53:50 +00:00
' object ' : bad_url ,
2023-07-07 04:16:04 +00:00
}
got = self . post ( ' /user.com/inbox ' , json = bad )
2022-12-10 17:01:04 +00:00
# bad object, should ignore activity
2023-07-07 04:16:04 +00:00
self . assertEqual ( 204 , got . status_code )
2022-12-10 17:01:04 +00:00
mock_post . assert_not_called ( )
2023-02-12 06:53:50 +00:00
2023-10-24 17:46:57 +00:00
expected = {
* * as2 . to_as1 ( bad ) ,
' actor ' : as2 . to_as1 ( ACTOR ) ,
' object ' : ' https://Testing – Brid.gy – Post to Mastodon 3/ ' ,
}
2023-07-07 04:16:04 +00:00
self . assert_object ( id ,
2023-10-24 17:46:57 +00:00
our_as1 = expected ,
2023-07-07 04:16:04 +00:00
users = [ self . swentel_key ] ,
source_protocol = ' activitypub ' ,
status = ' ignored ' ,
)
2023-02-12 06:53:50 +00:00
self . assertIsNone ( Object . get_by_id ( bad_url ) )
2022-12-10 17:01:04 +00:00
2023-02-16 04:10:17 +00:00
@patch ( ' activitypub.logger.info ' , side_effect = logging . info )
2023-03-08 21:10:41 +00:00
@patch ( ' common.logger.info ' , side_effect = logging . info )
2023-06-28 05:31:48 +00:00
@patch ( ' oauth_dropins.webutil.appengine_info.DEBUG ' , False )
2023-03-08 21:10:41 +00:00
def test_inbox_verify_http_signature ( self , mock_common_log , mock_activitypub_log ,
_ , mock_get , ___ ) :
2023-02-15 18:57:11 +00:00
# actor with a public key
2023-03-29 19:48:50 +00:00
self . key_id_obj . key . delete ( )
2023-04-03 03:36:23 +00:00
protocol . objects_cache . clear ( )
2023-07-25 01:16:50 +00:00
actor_as2 = {
2023-02-15 18:57:11 +00:00
* * ACTOR ,
' publicKey ' : {
2023-02-16 04:10:17 +00:00
' id ' : ' http://my/key/id#unused ' ,
' owner ' : ' http://own/er ' ,
2023-02-15 18:57:11 +00:00
' publicKeyPem ' : self . user . public_pem ( ) . decode ( ) ,
} ,
2023-07-25 01:16:50 +00:00
}
mock_get . return_value = self . as2_resp ( actor_as2 )
2023-02-15 18:57:11 +00:00
# valid signature
2023-02-26 13:34:15 +00:00
body = json_dumps ( NOTE )
2023-05-30 23:36:18 +00:00
headers = self . sign ( ' /ap/sharedInbox ' , json_dumps ( NOTE ) )
resp = self . client . post ( ' /ap/sharedInbox ' , data = body , headers = headers )
2023-07-07 04:16:04 +00:00
self . assertEqual ( 204 , resp . status_code , resp . get_data ( as_text = True ) )
2023-02-16 04:10:17 +00:00
mock_get . assert_has_calls ( (
self . as2_req ( ' http://my/key/id ' ) ,
) )
2023-03-08 21:10:41 +00:00
mock_activitypub_log . assert_any_call ( ' HTTP Signature verified! ' )
2023-02-15 18:57:11 +00:00
2023-07-25 01:16:50 +00:00
# valid signature, Object has no key
self . key_id_obj . as2 = ACTOR
self . key_id_obj . put ( )
resp = self . client . post ( ' /ap/sharedInbox ' , data = body , headers = headers )
self . assertEqual ( 401 , resp . status_code , resp . get_data ( as_text = True ) )
# valid signature, Object has our_as1 instead of as2
2024-01-08 21:03:44 +00:00
self . key_id_obj . clear ( )
2023-07-25 01:16:50 +00:00
self . key_id_obj . our_as1 = as2 . to_as1 ( actor_as2 )
self . key_id_obj . put ( )
resp = self . client . post ( ' /ap/sharedInbox ' , data = body , headers = headers )
self . assertEqual ( 204 , resp . status_code , resp . get_data ( as_text = True ) )
mock_activitypub_log . assert_any_call ( ' HTTP Signature verified! ' )
2023-02-16 04:10:17 +00:00
# invalid signature, missing keyId
2023-03-08 21:10:41 +00:00
protocol . seen_ids . clear ( )
2023-02-16 04:10:17 +00:00
obj_key = ndb . Key ( Object , NOTE [ ' id ' ] )
obj_key . delete ( )
2023-05-30 23:36:18 +00:00
resp = self . client . post ( ' /ap/sharedInbox ' , data = body , headers = {
2023-02-16 04:10:17 +00:00
* * headers ,
' signature ' : headers [ ' signature ' ] . replace (
' keyId= " http://my/key/id#unused " , ' , ' ' ) ,
} )
2023-02-26 13:34:15 +00:00
self . assertEqual ( 401 , resp . status_code )
self . assertEqual ( { ' error ' : ' HTTP Signature missing keyId ' } , resp . json )
2023-04-04 14:14:31 +00:00
mock_common_log . assert_any_call ( ' Returning 401: HTTP Signature missing keyId ' , exc_info = None )
2023-02-16 04:10:17 +00:00
2023-02-15 18:57:11 +00:00
# invalid signature, content changed
2023-03-08 21:10:41 +00:00
protocol . seen_ids . clear ( )
2023-02-15 18:57:11 +00:00
obj_key = ndb . Key ( Object , NOTE [ ' id ' ] )
obj_key . delete ( )
2023-02-15 20:41:29 +00:00
2023-05-30 23:36:18 +00:00
resp = self . client . post ( ' /ap/sharedInbox ' , json = { * * NOTE , ' content ' : ' z ' } , headers = headers )
2023-02-26 13:34:15 +00:00
self . assertEqual ( 401 , resp . status_code )
self . assertEqual ( { ' error ' : ' Invalid Digest header, required for HTTP Signature ' } ,
resp . json )
2023-04-04 14:14:31 +00:00
mock_common_log . assert_any_call ( ' Returning 401: Invalid Digest header, required for HTTP Signature ' , exc_info = None )
2023-02-15 18:57:11 +00:00
# invalid signature, header changed
2023-03-08 21:10:41 +00:00
protocol . seen_ids . clear ( )
2023-02-15 18:57:11 +00:00
obj_key . delete ( )
2023-02-15 20:41:29 +00:00
2023-05-30 23:36:18 +00:00
resp = self . client . post ( ' /ap/sharedInbox ' , data = body , headers = { * * headers , ' Date ' : ' X ' } )
2023-02-26 13:34:15 +00:00
self . assertEqual ( 401 , resp . status_code )
self . assertEqual ( { ' error ' : ' HTTP Signature verification failed ' } , resp . json )
2023-04-04 14:14:31 +00:00
mock_common_log . assert_any_call ( ' Returning 401: HTTP Signature verification failed ' , exc_info = None )
2023-02-15 20:41:29 +00:00
# no signature
2023-03-08 21:10:41 +00:00
protocol . seen_ids . clear ( )
2023-02-15 20:41:29 +00:00
obj_key . delete ( )
2023-05-30 23:36:18 +00:00
resp = self . client . post ( ' /ap/sharedInbox ' , json = NOTE )
2023-02-26 13:34:15 +00:00
self . assertEqual ( 401 , resp . status_code , resp . get_data ( as_text = True ) )
self . assertEqual ( { ' error ' : ' No HTTP Signature ' } , resp . json )
2023-04-04 14:14:31 +00:00
mock_common_log . assert_any_call ( ' Returning 401: No HTTP Signature ' , exc_info = None )
2023-02-15 18:57:11 +00:00
2023-03-10 03:56:04 +00:00
def test_delete_actor ( self , * mocks ) :
2023-10-19 22:01:19 +00:00
deleted = self . make_user ( DELETE [ ' actor ' ] , cls = ActivityPub )
follower = Follower . get_or_create ( to = self . user , from_ = deleted )
followee = Follower . get_or_create ( to = deleted , from_ = Fake ( id = ' fake:user ' ) )
2022-11-21 20:14:37 +00:00
# other unrelated follower
2023-10-19 22:01:19 +00:00
other = self . make_user ( ' https://mas.to/users/other ' , cls = ActivityPub )
other = Follower . get_or_create ( to = self . user , from_ = other )
2022-11-21 20:14:37 +00:00
self . assertEqual ( 3 , Follower . query ( ) . count ( ) )
2023-05-30 23:36:18 +00:00
got = self . post ( ' /ap/sharedInbox ' , json = DELETE )
2023-07-03 16:05:18 +00:00
self . assertEqual ( 204 , got . status_code )
2022-11-21 20:14:37 +00:00
self . assertEqual ( ' inactive ' , follower . key . get ( ) . status )
self . assertEqual ( ' inactive ' , followee . key . get ( ) . status )
self . assertEqual ( ' active ' , other . key . get ( ) . status )
2020-03-01 04:28:53 +00:00
2023-03-27 21:18:24 +00:00
def test_delete_actor_not_fetchable ( self , _ , mock_get , ___ ) :
2023-03-29 19:48:50 +00:00
self . key_id_obj . key . delete ( )
2023-04-03 03:36:23 +00:00
protocol . objects_cache . clear ( )
2023-03-10 03:56:04 +00:00
mock_get . return_value = requests_response ( status = 410 )
2023-05-30 23:36:18 +00:00
got = self . post ( ' /ap/sharedInbox ' , json = { * * DELETE , ' object ' : ' http://my/key/id ' } )
2023-03-10 03:56:04 +00:00
self . assertEqual ( 202 , got . status_code )
2023-03-29 19:48:50 +00:00
def test_delete_actor_empty_deleted_object ( self , _ , mock_get , ___ ) :
self . key_id_obj . as2 = None
self . key_id_obj . deleted = True
self . key_id_obj . put ( )
2023-04-03 03:36:23 +00:00
protocol . objects_cache . clear ( )
2023-03-29 19:48:50 +00:00
2023-05-30 23:36:18 +00:00
got = self . post ( ' /ap/sharedInbox ' , json = { * * DELETE , ' object ' : ' http://my/key/id ' } )
2023-03-29 19:48:50 +00:00
self . assertEqual ( 202 , got . status_code )
mock_get . assert_not_called ( )
2023-02-15 18:57:11 +00:00
def test_delete_note ( self , _ , mock_get , ___ ) :
2023-08-08 17:26:00 +00:00
obj = Object ( id = ' http://an/obj ' )
2023-02-14 22:30:00 +00:00
obj . put ( )
2023-02-12 05:46:47 +00:00
2023-02-15 18:57:11 +00:00
mock_get . side_effect = [
self . as2_resp ( ACTOR ) ,
]
2023-02-12 06:53:50 +00:00
delete = {
2023-02-12 05:46:47 +00:00
* * DELETE ,
' object ' : ' http://an/obj ' ,
2023-02-12 06:53:50 +00:00
}
2023-05-30 23:36:18 +00:00
resp = self . post ( ' /ap/sharedInbox ' , json = delete )
2023-07-03 16:05:18 +00:00
self . assertEqual ( 204 , resp . status_code )
2023-02-14 22:30:00 +00:00
self . assertTrue ( obj . key . get ( ) . deleted )
2023-07-03 16:05:18 +00:00
self . assert_object ( delete [ ' id ' ] ,
our_as1 = {
* * as2 . to_as1 ( delete ) ,
' actor ' : as2 . to_as1 ( ACTOR ) ,
} ,
type = ' delete ' ,
source_protocol = ' activitypub ' ,
status = ' ignored ' ,
users = [ ActivityPub ( id = ' https://mas.to/users/swentel ' ) . key ] )
2023-02-12 05:46:47 +00:00
2023-07-03 16:05:18 +00:00
obj . populate ( deleted = True , as2 = None )
self . assert_entities_equal ( obj ,
protocol . objects_cache [ ' http://an/obj ' ] ,
2023-06-22 21:27:02 +00:00
ignore = [ ' expire ' , ' created ' , ' updated ' ] )
2023-02-14 22:30:00 +00:00
2023-02-15 18:57:11 +00:00
def test_update_note ( self , * mocks ) :
2023-02-24 13:25:29 +00:00
Object ( id = ' https://a/note ' , as2 = { } ) . put ( )
2023-02-15 18:57:11 +00:00
self . _test_update ( * mocks )
2023-02-12 06:23:01 +00:00
2023-02-15 18:57:11 +00:00
def test_update_unknown ( self , * mocks ) :
self . _test_update ( * mocks )
def _test_update ( self , _ , mock_get , ___ ) :
mock_get . side_effect = [
self . as2_resp ( ACTOR ) ,
]
2023-02-12 06:23:01 +00:00
2023-05-30 23:36:18 +00:00
resp = self . post ( ' /ap/sharedInbox ' , json = UPDATE_NOTE )
2023-07-07 04:16:04 +00:00
self . assertEqual ( 204 , resp . status_code )
2023-02-12 06:23:01 +00:00
2023-07-07 04:16:04 +00:00
note_as1 = as2 . to_as1 ( {
* * UPDATE_NOTE [ ' object ' ] ,
' author ' : { ' id ' : ' https://mas.to/users/swentel ' } ,
} )
2023-06-27 03:22:06 +00:00
self . assert_object ( ' https://a/note ' ,
type = ' note ' ,
2023-07-07 04:16:04 +00:00
our_as1 = note_as1 ,
2023-02-24 03:17:26 +00:00
source_protocol = ' activitypub ' )
2023-07-07 04:16:04 +00:00
update_as1 = {
* * as2 . to_as1 ( UPDATE_NOTE ) ,
' object ' : note_as1 ,
' actor ' : as2 . to_as1 ( ACTOR ) ,
}
2023-06-27 03:22:06 +00:00
self . assert_object ( UPDATE_NOTE [ ' id ' ] ,
source_protocol = ' activitypub ' ,
type = ' update ' ,
2023-07-07 04:16:04 +00:00
status = ' ignored ' ,
our_as1 = update_as1 ,
users = [ self . swentel_key ] )
2023-01-26 03:44:48 +00:00
2023-02-14 22:30:00 +00:00
self . assert_entities_equal ( Object . get_by_id ( ' https://a/note ' ) ,
2023-04-03 03:36:23 +00:00
protocol . objects_cache [ ' https://a/note ' ] )
2023-02-14 22:30:00 +00:00
2021-09-01 15:19:38 +00:00
def test_inbox_webmention_discovery_connection_fails ( self , mock_head ,
mock_get , mock_post ) :
mock_get . side_effect = [
# source actor
2023-01-07 17:18:11 +00:00
self . as2_resp ( LIKE_WITH_ACTOR [ ' actor ' ] ) ,
2023-06-14 20:46:13 +00:00
# protocol inference
requests_response ( test_web . NOTE_HTML ) ,
requests_response ( test_web . NOTE_HTML ) ,
2021-09-01 15:19:38 +00:00
# target post webmention discovery
ReadTimeoutError ( None , None , None ) ,
]
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = LIKE )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2021-09-01 15:19:38 +00:00
2022-01-12 06:40:24 +00:00
def test_inbox_no_webmention_endpoint ( self , mock_head , mock_get , mock_post ) :
mock_get . side_effect = [
# source actor
2023-01-07 17:18:11 +00:00
self . as2_resp ( LIKE_WITH_ACTOR [ ' actor ' ] ) ,
2023-06-14 20:46:13 +00:00
# protocol inference
requests_response ( test_web . NOTE_HTML ) ,
requests_response ( test_web . NOTE_HTML ) ,
2022-01-12 06:40:24 +00:00
# target post webmention discovery
2023-03-19 22:43:55 +00:00
HTML ,
2022-01-12 06:40:24 +00:00
]
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = LIKE )
2023-10-31 19:49:15 +00:00
self . assertEqual ( 202 , got . status_code )
2022-01-12 06:40:24 +00:00
2023-03-19 22:43:55 +00:00
self . assert_object ( ' http://mas.to/like#ok ' ,
2023-07-17 15:31:28 +00:00
notify = [ self . user . key ] ,
users = [ self . masto_actor_key ] ,
2023-01-29 22:13:58 +00:00
source_protocol = ' activitypub ' ,
2023-07-07 04:16:04 +00:00
status = ' ignored ' ,
2023-06-27 03:22:06 +00:00
our_as1 = as2 . to_as1 ( LIKE_WITH_ACTOR ) ,
2023-01-30 01:30:11 +00:00
type = ' like ' ,
object_ids = [ LIKE [ ' object ' ] ] )
2022-11-22 02:46:10 +00:00
2023-02-13 05:58:59 +00:00
def test_inbox_id_already_seen ( self , * mocks ) :
2023-02-24 13:25:29 +00:00
obj_key = Object ( id = FOLLOW_WRAPPED [ ' id ' ] , as2 = { } ) . put ( )
2023-02-13 05:58:59 +00:00
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = FOLLOW_WRAPPED )
2023-07-07 04:16:04 +00:00
self . assertEqual ( 204 , got . status_code )
2023-02-13 05:58:59 +00:00
self . assertEqual ( 0 , Follower . query ( ) . count ( ) )
2023-02-13 06:17:04 +00:00
# second time should use in memory cache
obj_key . delete ( )
2023-03-19 22:43:55 +00:00
got = self . post ( ' /user.com/inbox ' , json = FOLLOW_WRAPPED )
2023-07-08 04:50:43 +00:00
self . assertEqual ( 204 , got . status_code )
2023-02-13 06:17:04 +00:00
self . assertEqual ( 0 , Follower . query ( ) . count ( ) )
2023-10-16 18:13:38 +00:00
def test_inbox_http_sig_is_not_actor_author ( self , mock_head , mock_get , mock_post ) :
mock_get . side_effect = [
self . as2_resp ( ACTOR ) , # author
]
with self . assertLogs ( ) as logs :
got = self . post ( ' /user.com/inbox ' , json = {
* * NOTE_OBJECT ,
2024-01-13 03:52:49 +00:00
' author ' : ' https://al/ice ' ,
2023-10-16 18:13:38 +00:00
} )
self . assertEqual ( 204 , got . status_code , got . get_data ( as_text = True ) )
self . assertIn (
2024-01-13 03:52:49 +00:00
" WARNING:protocol:actor https://al/ice isn ' t authed user http://my/key/id " ,
2023-10-16 18:13:38 +00:00
logs . output )
2023-05-30 23:36:18 +00:00
def test_followers_collection_unknown_user ( self , * _ ) :
2023-02-08 02:25:24 +00:00
resp = self . client . get ( ' /nope.com/followers ' )
2022-11-22 02:46:10 +00:00
self . assertEqual ( 404 , resp . status_code )
2023-05-30 23:36:18 +00:00
def test_followers_collection_empty ( self , * _ ) :
2023-03-19 22:43:55 +00:00
resp = self . client . get ( ' /user.com/followers ' )
2022-11-22 02:46:10 +00:00
self . assertEqual ( 200 , resp . status_code )
self . assertEqual ( {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' http://localhost/user.com/followers ' ,
2022-11-22 02:46:10 +00:00
' type ' : ' Collection ' ,
2023-03-19 22:43:55 +00:00
' summary ' : " user.com ' s followers " ,
2022-11-22 02:46:10 +00:00
' totalItems ' : 0 ,
2023-01-20 15:02:55 +00:00
' first ' : {
' type ' : ' CollectionPage ' ,
2023-03-19 22:43:55 +00:00
' partOf ' : ' http://localhost/user.com/followers ' ,
2023-01-20 15:02:55 +00:00
' items ' : [ ] ,
} ,
2022-11-22 02:46:10 +00:00
} , resp . json )
2023-01-20 15:02:55 +00:00
def store_followers ( self ) :
2023-06-15 22:09:03 +00:00
follow = Object ( id = FOLLOW_WITH_ACTOR [ ' id ' ] , as2 = FOLLOW_WITH_ACTOR ) . put ( )
2023-06-07 21:24:00 +00:00
2023-06-16 04:22:20 +00:00
Follower . get_or_create (
to = self . user ,
2023-06-23 19:22:37 +00:00
from_ = self . make_user ( ' http://bar ' , cls = ActivityPub , obj_as2 = ACTOR ) ,
2023-06-16 04:22:20 +00:00
follow = follow )
Follower . get_or_create (
2024-01-13 03:52:49 +00:00
to = self . make_user ( ' https://other/actor ' , cls = ActivityPub ) ,
2023-06-16 04:22:20 +00:00
from_ = self . user )
Follower . get_or_create (
to = self . user ,
2023-06-23 19:22:37 +00:00
from_ = self . make_user ( ' http://baz ' , cls = ActivityPub , obj_as2 = ACTOR ) ,
2023-06-16 04:22:20 +00:00
follow = follow )
Follower . get_or_create (
to = self . user ,
2024-01-13 03:52:49 +00:00
from_ = self . make_user ( ' fake:baj ' , cls = Fake ) ,
2023-06-16 04:22:20 +00:00
status = ' inactive ' )
2022-11-22 02:46:10 +00:00
2023-05-30 23:36:18 +00:00
def test_followers_collection_fake ( self , * _ ) :
2024-01-13 03:52:49 +00:00
self . make_user ( ' fake:foo ' , cls = Fake )
2023-05-30 23:36:18 +00:00
2024-01-13 03:52:49 +00:00
resp = self . client . get ( ' /ap/fake:foo/followers ' ,
2023-09-27 03:04:49 +00:00
base_url = ' https://fa.brid.gy ' )
2023-05-30 23:36:18 +00:00
self . assertEqual ( 200 , resp . status_code )
self . assertEqual ( {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2024-01-13 03:52:49 +00:00
' id ' : ' https://fa.brid.gy/ap/fake:foo/followers ' ,
2023-05-30 23:36:18 +00:00
' type ' : ' Collection ' ,
2024-01-13 03:52:49 +00:00
' summary ' : " fake:foo ' s followers " ,
2023-05-30 23:36:18 +00:00
' totalItems ' : 0 ,
' first ' : {
' type ' : ' CollectionPage ' ,
2024-01-13 03:52:49 +00:00
' partOf ' : ' https://fa.brid.gy/ap/fake:foo/followers ' ,
2023-05-30 23:36:18 +00:00
' items ' : [ ] ,
} ,
} , resp . json )
def test_followers_collection ( self , * _ ) :
2023-01-20 15:02:55 +00:00
self . store_followers ( )
2023-03-19 22:43:55 +00:00
resp = self . client . get ( ' /user.com/followers ' )
2022-11-22 02:46:10 +00:00
self . assertEqual ( 200 , resp . status_code )
2024-01-29 02:15:37 +00:00
self . assert_equals ( {
2022-11-22 02:46:10 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' http://localhost/user.com/followers ' ,
2022-11-22 02:46:10 +00:00
' type ' : ' Collection ' ,
2023-03-19 22:43:55 +00:00
' summary ' : " user.com ' s followers " ,
2022-11-22 02:46:10 +00:00
' totalItems ' : 2 ,
2023-01-20 15:02:55 +00:00
' first ' : {
' type ' : ' CollectionPage ' ,
2023-03-19 22:43:55 +00:00
' partOf ' : ' http://localhost/user.com/followers ' ,
2023-01-20 15:02:55 +00:00
' items ' : [ ACTOR , ACTOR ] ,
} ,
} , resp . json )
2023-03-08 21:10:41 +00:00
@patch ( ' models.PAGE_SIZE ' , 1 )
2023-05-30 23:36:18 +00:00
def test_followers_collection_page ( self , * _ ) :
2023-01-20 15:02:55 +00:00
self . store_followers ( )
before = ( datetime . utcnow ( ) + timedelta ( seconds = 1 ) ) . isoformat ( )
2023-06-23 19:22:37 +00:00
next = Follower . query ( Follower . from_ == ActivityPub ( id = ' http://baz ' ) . key ,
2023-06-07 21:24:00 +00:00
Follower . to == self . user . key ,
) . get ( ) . updated . isoformat ( )
2023-01-20 15:02:55 +00:00
2023-03-19 22:43:55 +00:00
resp = self . client . get ( f ' /user.com/followers?before= { before } ' )
2023-01-20 15:02:55 +00:00
self . assertEqual ( 200 , resp . status_code )
2024-01-29 02:15:37 +00:00
self . assert_equals ( {
2023-01-20 15:02:55 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-03-19 22:43:55 +00:00
' id ' : f ' http://localhost/user.com/followers?before= { before } ' ,
2023-01-20 15:02:55 +00:00
' type ' : ' CollectionPage ' ,
2023-03-19 22:43:55 +00:00
' partOf ' : ' http://localhost/user.com/followers ' ,
' next ' : f ' http://localhost/user.com/followers?before= { next } ' ,
' prev ' : f ' http://localhost/user.com/followers?after= { before } ' ,
2023-01-20 15:02:55 +00:00
' items ' : [ ACTOR ] ,
2022-11-22 02:46:10 +00:00
} , resp . json )
2023-05-30 23:36:18 +00:00
def test_following_collection_unknown_user ( self , * _ ) :
2023-02-08 02:25:24 +00:00
resp = self . client . get ( ' /nope.com/following ' )
2022-11-22 02:46:10 +00:00
self . assertEqual ( 404 , resp . status_code )
2023-05-30 23:36:18 +00:00
def test_following_collection_empty ( self , * _ ) :
2023-03-19 22:43:55 +00:00
resp = self . client . get ( ' /user.com/following ' )
2022-11-22 02:46:10 +00:00
self . assertEqual ( 200 , resp . status_code )
self . assertEqual ( {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' http://localhost/user.com/following ' ,
' summary ' : " user.com ' s following " ,
2022-11-22 02:46:10 +00:00
' type ' : ' Collection ' ,
' totalItems ' : 0 ,
2023-01-20 15:02:55 +00:00
' first ' : {
' type ' : ' CollectionPage ' ,
2023-03-19 22:43:55 +00:00
' partOf ' : ' http://localhost/user.com/following ' ,
2023-01-20 15:02:55 +00:00
' items ' : [ ] ,
} ,
2022-11-22 02:46:10 +00:00
} , resp . json )
2023-01-20 15:02:55 +00:00
def store_following ( self ) :
2023-06-15 22:09:03 +00:00
follow = Object ( id = FOLLOW_WITH_ACTOR [ ' id ' ] , as2 = FOLLOW_WITH_ACTOR ) . put ( )
2023-06-07 21:24:00 +00:00
2023-06-16 04:22:20 +00:00
Follower . get_or_create (
2023-06-23 19:22:37 +00:00
to = self . make_user ( ' http://bar ' , cls = ActivityPub , obj_as2 = ACTOR ) ,
2023-06-16 04:22:20 +00:00
from_ = self . user ,
follow = follow )
Follower . get_or_create (
to = self . user ,
2024-01-13 03:52:49 +00:00
from_ = self . make_user ( ' https://other/actor ' , cls = ActivityPub ) )
2023-06-16 04:22:20 +00:00
Follower . get_or_create (
2023-06-23 19:22:37 +00:00
to = self . make_user ( ' http://baz ' , cls = ActivityPub , obj_as2 = ACTOR ) ,
2023-06-16 04:22:20 +00:00
from_ = self . user , follow = follow )
Follower . get_or_create (
2024-01-13 03:52:49 +00:00
to = self . make_user ( ' http://ba/j ' , cls = ActivityPub ) ,
2023-06-16 04:22:20 +00:00
from_ = self . user ,
status = ' inactive ' )
2022-11-22 02:46:10 +00:00
2023-05-30 23:36:18 +00:00
def test_following_collection ( self , * _ ) :
2023-01-20 15:02:55 +00:00
self . store_following ( )
2023-03-19 22:43:55 +00:00
resp = self . client . get ( ' /user.com/following ' )
2022-11-22 02:46:10 +00:00
self . assertEqual ( 200 , resp . status_code )
2024-01-29 02:15:37 +00:00
self . assert_equals ( {
2022-11-22 02:46:10 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' http://localhost/user.com/following ' ,
' summary ' : " user.com ' s following " ,
2022-11-22 02:46:10 +00:00
' type ' : ' Collection ' ,
' totalItems ' : 2 ,
2023-01-20 15:02:55 +00:00
' first ' : {
' type ' : ' CollectionPage ' ,
2023-03-19 22:43:55 +00:00
' partOf ' : ' http://localhost/user.com/following ' ,
2023-01-20 15:02:55 +00:00
' items ' : [ ACTOR , ACTOR ] ,
} ,
} , resp . json )
2023-03-08 21:10:41 +00:00
@patch ( ' models.PAGE_SIZE ' , 1 )
2023-05-30 23:36:18 +00:00
def test_following_collection_page ( self , * _ ) :
2023-01-20 15:02:55 +00:00
self . store_following ( )
after = datetime ( 1900 , 1 , 1 ) . isoformat ( )
2024-04-17 18:36:28 +00:00
prev = Follower . query ( Follower . to == ActivityPub ( id = ' http://bar ' ) . key ,
2023-06-07 21:24:00 +00:00
Follower . from_ == self . user . key ,
) . get ( ) . updated . isoformat ( )
2023-01-20 15:02:55 +00:00
2023-03-19 22:43:55 +00:00
resp = self . client . get ( f ' /user.com/following?after= { after } ' )
2023-01-20 15:02:55 +00:00
self . assertEqual ( 200 , resp . status_code )
2024-01-29 02:15:37 +00:00
self . assert_equals ( {
2023-01-20 15:02:55 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-03-19 22:43:55 +00:00
' id ' : f ' http://localhost/user.com/following?after= { after } ' ,
2023-01-20 15:02:55 +00:00
' type ' : ' CollectionPage ' ,
2023-03-19 22:43:55 +00:00
' partOf ' : ' http://localhost/user.com/following ' ,
' prev ' : f ' http://localhost/user.com/following?after= { prev } ' ,
' next ' : f ' http://localhost/user.com/following?before= { after } ' ,
2023-01-20 15:02:55 +00:00
' items ' : [ ACTOR ] ,
2022-11-22 02:46:10 +00:00
} , resp . json )
2023-01-25 21:12:24 +00:00
2023-11-24 06:41:52 +00:00
def test_following_collection_head ( self , * _ ) :
resp = self . client . head ( f ' /user.com/following ' )
self . assertEqual ( 200 , resp . status_code )
self . assertEqual ( ' ' , resp . get_data ( as_text = True ) )
2023-11-27 19:18:12 +00:00
def test_following_collection_opted_out ( self , * _ ) :
self . user . obj . our_as1 [ ' summary ' ] = ' #nobridge '
self . user . obj . put ( )
self . user . put ( )
resp = self . client . get ( f ' /user.com/following ' , base_url = ' https://web.brid.gy ' )
self . assertEqual ( 404 , resp . status_code )
2023-11-23 05:40:36 +00:00
def test_outbox_fake_empty ( self , * _ ) :
self . make_user ( ' fake:foo ' , cls = Fake )
resp = self . client . get ( f ' /ap/fake:foo/outbox ' ,
2023-09-27 03:04:49 +00:00
base_url = ' https://fa.brid.gy ' )
2023-05-30 23:36:18 +00:00
self . assertEqual ( 200 , resp . status_code )
self . assertEqual ( {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-11-23 05:40:36 +00:00
' id ' : ' https://fa.brid.gy/ap/fake:foo/outbox ' ,
' summary ' : " fake:foo ' s outbox " ,
2023-05-30 23:36:18 +00:00
' type ' : ' OrderedCollection ' ,
2023-11-24 06:35:38 +00:00
' totalItems ' : 0 ,
2023-05-30 23:36:18 +00:00
' first ' : {
' type ' : ' CollectionPage ' ,
2023-11-23 05:40:36 +00:00
' partOf ' : ' https://fa.brid.gy/ap/fake:foo/outbox ' ,
2023-05-30 23:36:18 +00:00
' items ' : [ ] ,
} ,
} , resp . json )
2023-11-24 06:35:38 +00:00
def store_outbox_objects ( self , user ) :
for i , obj in enumerate ( [ REPLY , MENTION , LIKE , DELETE ] ) :
self . store_object ( id = obj [ ' id ' ] , users = [ user . key ] , as2 = obj )
@patch ( ' models.PAGE_SIZE ' , 2 )
2023-11-23 05:40:36 +00:00
def test_outbox_fake_objects ( self , * _ ) :
user = self . make_user ( ' fake:foo ' , cls = Fake )
2023-11-24 06:35:38 +00:00
self . store_outbox_objects ( user )
2023-11-23 05:40:36 +00:00
resp = self . client . get ( f ' /ap/fake:foo/outbox ' ,
base_url = ' https://fa.brid.gy ' )
self . assertEqual ( 200 , resp . status_code )
2023-11-24 06:35:38 +00:00
after = Object . get_by_id ( LIKE [ ' id ' ] ) . updated . isoformat ( )
2024-01-29 02:15:37 +00:00
self . assert_equals ( {
2023-11-23 05:40:36 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' id ' : ' https://fa.brid.gy/ap/fake:foo/outbox ' ,
' summary ' : " fake:foo ' s outbox " ,
' type ' : ' OrderedCollection ' ,
2023-11-24 06:35:38 +00:00
' totalItems ' : 4 ,
2023-11-23 05:40:36 +00:00
' first ' : {
' type ' : ' CollectionPage ' ,
' partOf ' : ' https://fa.brid.gy/ap/fake:foo/outbox ' ,
2023-11-24 06:35:38 +00:00
' items ' : [ DELETE , LIKE ] ,
' next ' : f ' https://fa.brid.gy/ap/fake:foo/outbox?before= { after } ' ,
2023-11-23 05:40:36 +00:00
} ,
} , resp . json )
2023-11-24 06:35:38 +00:00
@patch ( ' models.PAGE_SIZE ' , 2 )
def test_outbox_fake_objects_page ( self , * _ ) :
user = self . make_user ( ' fake:foo ' , cls = Fake )
self . store_outbox_objects ( user )
after = datetime ( 1900 , 1 , 1 ) . isoformat ( )
resp = self . client . get ( f ' /ap/fake:foo/outbox?after= { after } ' ,
base_url = ' https://fa.brid.gy ' )
self . assertEqual ( 200 , resp . status_code )
prev = Object . get_by_id ( MENTION [ ' id ' ] ) . updated . isoformat ( )
2024-01-29 02:15:37 +00:00
self . assert_equals ( {
2023-11-24 06:35:38 +00:00
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' id ' : f ' https://fa.brid.gy/ap/fake:foo/outbox?after= { after } ' ,
' type ' : ' CollectionPage ' ,
' partOf ' : ' https://fa.brid.gy/ap/fake:foo/outbox ' ,
' prev ' : f ' https://fa.brid.gy/ap/fake:foo/outbox?after= { prev } ' ,
' next ' : f ' https://fa.brid.gy/ap/fake:foo/outbox?before= { after } ' ,
' items ' : [ MENTION , REPLY ] ,
} , resp . json )
2023-11-23 05:40:36 +00:00
def test_outbox_web_empty ( self , * _ ) :
2023-03-19 22:43:55 +00:00
resp = self . client . get ( f ' /user.com/outbox ' )
2023-01-25 21:12:24 +00:00
self . assertEqual ( 200 , resp . status_code )
self . assertEqual ( {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-03-19 22:43:55 +00:00
' id ' : ' http://localhost/user.com/outbox ' ,
' summary ' : " user.com ' s outbox " ,
2023-01-25 21:12:24 +00:00
' type ' : ' OrderedCollection ' ,
2023-11-24 06:35:38 +00:00
' totalItems ' : 0 ,
2023-01-25 21:12:24 +00:00
' first ' : {
' type ' : ' CollectionPage ' ,
2023-03-19 22:43:55 +00:00
' partOf ' : ' http://localhost/user.com/outbox ' ,
2023-01-25 21:12:24 +00:00
' items ' : [ ] ,
} ,
} , resp . json )
2023-03-08 21:10:41 +00:00
2023-11-24 06:41:52 +00:00
def test_outbox_web_head ( self , * _ ) :
resp = self . client . head ( f ' /user.com/outbox ' )
self . assertEqual ( 200 , resp . status_code )
self . assertEqual ( ' ' , resp . get_data ( as_text = True ) )
2023-11-27 19:18:12 +00:00
def test_outbox_opted_out ( self , * _ ) :
self . user . obj . our_as1 [ ' summary ' ] = ' #nobridge '
self . user . obj . put ( )
self . user . put ( )
resp = self . client . get ( f ' /ap/user.com/outbox ' ,
base_url = ' https://web.brid.gy ' )
self . assertEqual ( 404 , resp . status_code )
2023-03-08 21:10:41 +00:00
2023-05-26 23:07:36 +00:00
class ActivityPubUtilsTest ( TestCase ) :
2023-03-08 21:10:41 +00:00
def setUp ( self ) :
super ( ) . setUp ( )
2023-11-21 00:20:19 +00:00
self . user = self . make_user ( ' user.com ' , cls = Web , has_hcard = True , obj_as2 = ACTOR )
2023-06-01 01:34:33 +00:00
2023-12-14 23:48:02 +00:00
for obj in ACTOR_BASE , ACTOR_FAKE :
obj [ ' publicKey ' ] [ ' publicKeyPem ' ] = self . user . public_pem ( ) . decode ( )
2023-06-23 19:22:37 +00:00
def test_put_validates_id ( self , * _ ) :
for bad in (
' ' ,
' not a url ' ,
' ftp://not.web/url ' ,
' https:///no/domain ' ,
' https://fed.brid.gy/foo ' ,
' https://ap.brid.gy/foo ' ,
' http://localhost/foo ' ,
) :
with self . assertRaises ( AssertionError ) :
ActivityPub ( id = bad ) . put ( )
2023-06-13 20:17:11 +00:00
def test_owns_id ( self ) :
2024-01-26 19:37:34 +00:00
self . assertIsNone ( ActivityPub . owns_id ( ' http://foo ' ) )
self . assertIsNone ( ActivityPub . owns_id ( ' https://bar/baz ' ) )
self . assertFalse ( ActivityPub . owns_id ( ' at://did:plc:foo/bar/123 ' ) )
self . assertFalse ( ActivityPub . owns_id ( ' e45fab982 ' ) )
self . assertFalse ( ActivityPub . owns_id ( ' https://twitter.com/foo ' ) )
self . assertFalse ( ActivityPub . owns_id ( ' https://fed.brid.gy/foo ' ) )
2023-07-02 21:55:05 +00:00
2023-09-22 19:14:50 +00:00
def test_owns_handle ( self ) :
for handle in ( ' @user@instance ' , ' user@instance.com ' , ' user.com@instance.com ' ,
' user@instance ' ) :
with self . subTest ( handle = handle ) :
assert ActivityPub . owns_handle ( handle )
for handle in ( ' instance ' , ' instance.com ' , ' @user ' , ' @user.com ' ,
2024-01-15 15:56:59 +00:00
' http://user.com ' , ' @user@web.brid.gy ' , ' @user@localhost ' ) :
2023-09-22 19:14:50 +00:00
with self . subTest ( handle = handle ) :
2024-01-15 15:56:59 +00:00
self . assertEqual ( False , ActivityPub . owns_handle ( handle ) )
2023-09-22 19:14:50 +00:00
2023-09-22 20:11:15 +00:00
def test_handle_to_id_stored ( self ) :
self . make_user ( id = ' http://inst.com/@user ' , cls = ActivityPub )
self . assertEqual ( ' http://inst.com/@user ' ,
ActivityPub . handle_to_id ( ' @user@inst.com ' ) )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-09-22 20:11:15 +00:00
def test_handle_to_id_fetch ( self , mock_get ) :
2023-09-23 20:53:17 +00:00
mock_get . return_value = requests_response ( test_webfinger . WEBFINGER )
2023-09-22 20:11:15 +00:00
self . assertEqual ( ' http://localhost/user.com ' ,
ActivityPub . handle_to_id ( ' @user@inst.com ' ) )
self . assert_req (
mock_get ,
' https://inst.com/.well-known/webfinger?resource=acct:user@inst.com ' )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' , return_value = requests_response ( { } ) )
2023-09-22 20:11:15 +00:00
def test_handle_to_id_not_found ( self , mock_get ) :
self . assertIsNone ( ActivityPub . handle_to_id ( ' @user@inst.com ' ) )
self . assert_req (
mock_get ,
' https://inst.com/.well-known/webfinger?resource=acct:user@inst.com ' )
2023-03-08 21:10:41 +00:00
def test_postprocess_as2_multiple_in_reply_tos ( self ) :
self . assert_equals ( {
' id ' : ' http://localhost/r/xyz ' ,
' inReplyTo ' : ' foo ' ,
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
2023-06-16 20:16:17 +00:00
} , postprocess_as2 ( {
2023-03-08 21:10:41 +00:00
' id ' : ' xyz ' ,
' inReplyTo ' : [ ' foo ' , ' bar ' ] ,
2023-03-20 21:28:14 +00:00
} ) )
2023-03-08 21:10:41 +00:00
def test_postprocess_as2_multiple_url ( self ) :
self . assert_equals ( {
' id ' : ' http://localhost/r/xyz ' ,
' url ' : [ ' http://localhost/r/foo ' , ' http://localhost/r/bar ' ] ,
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
2023-06-16 20:16:17 +00:00
} , postprocess_as2 ( {
2023-03-08 21:10:41 +00:00
' id ' : ' xyz ' ,
' url ' : [ ' foo ' , ' bar ' ] ,
2023-03-20 21:28:14 +00:00
} ) )
2023-03-08 21:10:41 +00:00
def test_postprocess_as2_multiple_image ( self ) :
self . assert_equals ( {
' id ' : ' http://localhost/r/xyz ' ,
' attachment ' : [ { ' url ' : ' http://r/foo ' } , { ' url ' : ' http://r/bar ' } ] ,
' image ' : [ { ' url ' : ' http://r/foo ' } , { ' url ' : ' http://r/bar ' } ] ,
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
2023-06-16 20:16:17 +00:00
} , postprocess_as2 ( {
2023-03-08 21:10:41 +00:00
' id ' : ' xyz ' ,
' image ' : [ { ' url ' : ' http://r/foo ' } , { ' url ' : ' http://r/bar ' } ] ,
2023-03-20 21:28:14 +00:00
} ) )
2023-03-08 21:10:41 +00:00
def test_postprocess_as2_note ( self ) :
self . assert_equals ( {
2023-04-02 02:13:51 +00:00
' id ' : ' http://localhost/r/xyz ' ,
' type ' : ' Note ' ,
2023-10-20 20:37:38 +00:00
' content ' : ' foo ' ,
' contentMap ' : { ' en ' : ' foo ' } ,
2023-04-02 02:13:51 +00:00
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
2023-06-16 20:16:17 +00:00
} , postprocess_as2 ( {
2023-03-08 21:10:41 +00:00
' id ' : ' xyz ' ,
' type ' : ' Note ' ,
2023-10-20 20:37:38 +00:00
' content ' : ' foo ' ,
2023-03-20 21:28:14 +00:00
} ) )
2023-03-08 21:10:41 +00:00
2023-03-14 21:59:28 +00:00
def test_postprocess_as2_hashtag ( self ) :
""" https://github.com/snarfed/bridgy-fed/issues/45 """
self . assert_equals ( {
' tag ' : [
{ ' type ' : ' Hashtag ' , ' name ' : ' #bar ' , ' href ' : ' bar ' } ,
{ ' type ' : ' Hashtag ' , ' name ' : ' #baz ' , ' href ' : ' http://localhost/hashtag/baz ' } ,
{ ' type ' : ' Mention ' , ' href ' : ' foo ' } ,
] ,
2023-10-26 19:02:57 +00:00
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
2023-10-12 20:55:17 +00:00
' cc ' : [ ' foo ' ] ,
2023-06-16 20:16:17 +00:00
} , postprocess_as2 ( {
2023-03-14 21:59:28 +00:00
' tag ' : [
{ ' name ' : ' bar ' , ' href ' : ' bar ' } ,
2023-06-20 18:22:54 +00:00
{ ' type ' : ' Tag ' , ' name ' : ' #baz ' } ,
2023-03-14 21:59:28 +00:00
# should leave alone
{ ' type ' : ' Mention ' , ' href ' : ' foo ' } ,
] ,
2023-03-20 21:28:14 +00:00
} ) )
2023-03-14 21:59:28 +00:00
2023-11-24 17:20:52 +00:00
def test_postprocess_as2_actor_url_attachments ( self ) :
got = postprocess_as2_actor ( as2 . from_as1 ( {
2023-06-16 04:22:20 +00:00
' objectType ' : ' person ' ,
' urls ' : [
{
' value ' : ' https://user.com/about-me ' ,
' displayName ' : ' Mrs. \u2615 Foo ' ,
} , {
' value ' : ' https://user.com/ ' ,
' displayName ' : ' should be ignored ' ,
} , {
' value ' : ' http://one ' ,
' displayName ' : ' one text ' ,
} , {
' value ' : ' https://two ' ,
' displayName ' : ' two title ' ,
} ,
]
2023-11-26 04:07:14 +00:00
} ) , user = self . user )
2023-06-16 04:22:20 +00:00
self . assert_equals ( [ {
' type ' : ' PropertyValue ' ,
' name ' : ' Mrs. ☕ Foo ' ,
2023-10-24 23:02:16 +00:00
' value ' : ' <a rel= " me " href= " https://user.com/about-me " ><span class= " invisible " >https://</span>user.com/about-me</a> ' ,
2023-06-23 05:32:50 +00:00
2023-06-16 04:22:20 +00:00
} , {
' type ' : ' PropertyValue ' ,
' name ' : ' Web site ' ,
2023-10-24 23:02:16 +00:00
' value ' : ' <a rel= " me " href= " https://user.com " ><span class= " invisible " >https://</span>user.com</a> ' ,
2023-06-16 04:22:20 +00:00
} , {
' type ' : ' PropertyValue ' ,
' name ' : ' one text ' ,
2023-10-24 23:02:16 +00:00
' value ' : ' <a rel= " me " href= " http://one " ><span class= " invisible " >http://</span>one</a> ' ,
2023-06-16 04:22:20 +00:00
} , {
' type ' : ' PropertyValue ' ,
' name ' : ' two title ' ,
2023-10-24 23:02:16 +00:00
' value ' : ' <a rel= " me " href= " https://two " ><span class= " invisible " >https://</span>two</a> ' ,
2023-06-16 04:22:20 +00:00
} ] , got [ ' attachment ' ] )
2023-11-29 22:51:56 +00:00
def test_postprocess_as2_actor_strips_acct_url ( self ) :
self . assert_equals ( ' http://localhost/r/http://user.com/ ' ,
postprocess_as2_actor ( as2 . from_as1 ( {
' objectType ' : ' person ' ,
' urls ' : [ ' http://user.com/ ' , ' acct:foo@bar ' ] ,
} ) , user = self . user ) [ ' url ' ] )
2023-11-24 17:20:52 +00:00
def test_postprocess_as2_actor_preserves_preferredUsername ( self ) :
2023-06-16 04:22:20 +00:00
# preferredUsername stays y.z despite user's username. since Mastodon
# queries Webfinger for preferredUsername@fed.brid.gy
# https://github.com/snarfed/bridgy-fed/issues/77#issuecomment-949955109
2023-11-24 17:20:52 +00:00
self . assertEqual ( ' user.com ' , postprocess_as2_actor ( {
2023-06-16 04:22:20 +00:00
' type ' : ' Person ' ,
' url ' : ' https://user.com/about-me ' ,
' preferredUsername ' : ' nick ' ,
' attachment ' : [ {
' type ' : ' PropertyValue ' ,
' name ' : ' nick ' ,
2023-10-24 23:02:16 +00:00
' value ' : ' <a rel= " me " href= " https://user.com/about-me " ><span class= " invisible " >https://</span>user.com/about-me</a> ' ,
2023-06-16 04:22:20 +00:00
} ] ,
2023-11-26 04:07:14 +00:00
} , user = self . user ) [ ' preferredUsername ' ] )
2023-06-16 04:22:20 +00:00
2024-01-26 14:52:03 +00:00
def test_postprocess_as2_actor_preferredUsername_is_domain ( self ) :
self . user . has_redirects = True
self . user . put ( )
self . user . obj . clear ( )
self . user . obj . as2 = {
' type ' : ' Person ' ,
' url ' : [ ' acct:eve@user.com ' ] ,
}
self . user . obj . put ( )
# preferredUsername stays y.z despite user's username
self . assertEqual ( ' user.com ' , postprocess_as2_actor ( {
' type ' : ' Person ' ,
} , user = self . user ) [ ' preferredUsername ' ] )
2023-12-01 21:06:59 +00:00
def test_postprocess_as2_user_wrapped_id ( self ) :
for id in ' http://fed.brid.gy/user.com ' , ' http://fed.brid.gy/www.user.com ' :
got = postprocess_as2_actor ( as2 . from_as1 ( {
' objectType ' : ' person ' ,
' id ' : id ,
} ) , user = self . user )
self . assert_equals ( ' http://localhost/user.com ' , got [ ' id ' ] )
2023-10-12 20:55:17 +00:00
def test_postprocess_as2_mentions_into_cc ( self ) :
obj = copy . deepcopy ( MENTION_OBJECT )
del obj [ ' cc ' ]
self . assertEqual ( [ ' https://masto.foo/@other ' ] ,
postprocess_as2 ( obj ) [ ' cc ' ] )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-03-08 21:10:41 +00:00
def test_signed_get_redirects_manually_with_new_sig_headers ( self , mock_get ) :
mock_get . side_effect = [
requests_response ( status = 302 , redirected_url = ' http://second ' ,
allow_redirects = False ) ,
requests_response ( status = 200 , allow_redirects = False ) ,
]
2023-06-20 18:22:54 +00:00
activitypub . signed_get ( ' https://first ' )
2023-03-08 21:10:41 +00:00
first = mock_get . call_args_list [ 0 ] [ 1 ]
second = mock_get . call_args_list [ 1 ] [ 1 ]
self . assertNotEqual ( first [ ' headers ' ] , second [ ' headers ' ] )
2023-07-15 01:16:10 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-07-15 01:16:10 +00:00
def test_signed_get_redirects_to_relative_url ( self , mock_get ) :
mock_get . side_effect = [
# redirected URL is relative, we have to resolve it
requests_response ( status = 302 , redirected_url = ' /second ' ,
allow_redirects = False ) ,
requests_response ( status = 200 , allow_redirects = False ) ,
]
activitypub . signed_get ( ' https://first ' )
self . assertEqual ( ( ' https://first/second ' , ) , mock_get . call_args_list [ 1 ] [ 0 ] )
first = mock_get . call_args_list [ 0 ] [ 1 ]
second = mock_get . call_args_list [ 1 ] [ 1 ]
# headers are equal because host is the same
self . assertEqual ( first [ ' headers ' ] , second [ ' headers ' ] )
self . assertEqual (
2023-03-08 21:10:41 +00:00
first [ ' auth ' ] . header_signer . sign ( first [ ' headers ' ] , method = ' GET ' , path = ' / ' ) ,
second [ ' auth ' ] . header_signer . sign ( second [ ' headers ' ] , method = ' GET ' , path = ' / ' ) )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.post ' , return_value = requests_response ( status = 200 ) )
2024-01-06 21:59:31 +00:00
def test_signed_post_from_user_is_activitypub_use_instance_actor ( self , mock_post ) :
2023-11-26 04:07:14 +00:00
activitypub . signed_post ( ' https://url ' , from_user = ActivityPub ( id = ' http://fed ' ) )
2023-10-18 20:51:34 +00:00
self . assertEqual ( 1 , len ( mock_post . call_args_list ) )
args , kwargs = mock_post . call_args_list [ 0 ]
self . assertEqual ( ( ' https://url ' , ) , args )
rsa_key = kwargs [ ' auth ' ] . header_signer . _rsa . _key
2024-01-06 21:59:31 +00:00
self . assertEqual ( instance_actor ( ) . private_pem ( ) , rsa_key . exportKey ( ) )
2023-10-18 20:51:34 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.post ' )
2023-03-08 21:10:41 +00:00
def test_signed_post_ignores_redirect ( self , mock_post ) :
mock_post . side_effect = [
requests_response ( status = 302 , redirected_url = ' http://second ' ,
allow_redirects = False ) ,
]
2023-11-26 04:07:14 +00:00
resp = activitypub . signed_post ( ' https://first ' , from_user = self . user )
2023-03-08 21:10:41 +00:00
mock_post . assert_called_once ( )
self . assertEqual ( 302 , resp . status_code )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-03-08 21:10:41 +00:00
def test_fetch_direct ( self , mock_get ) :
mock_get . return_value = AS2
2023-04-03 14:53:15 +00:00
obj = Object ( id = ' http://orig ' )
ActivityPub . fetch ( obj )
self . assertEqual ( AS2_OBJ , obj . as2 )
2023-04-02 02:13:51 +00:00
2023-03-08 21:10:41 +00:00
mock_get . assert_has_calls ( (
self . as2_req ( ' http://orig ' ) ,
) )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-05-28 15:06:36 +00:00
def test_fetch_direct_ld_content_type ( self , mock_get ) :
mock_get . return_value = requests_response ( AS2_OBJ , headers = {
' Content-Type ' : ' application/ld+json; profile= " https://www.w3.org/ns/activitystreams " ' ,
} )
obj = Object ( id = ' http://orig ' )
ActivityPub . fetch ( obj )
self . assertEqual ( AS2_OBJ , obj . as2 )
mock_get . assert_has_calls ( (
self . as2_req ( ' http://orig ' ) ,
) )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-03-08 21:10:41 +00:00
def test_fetch_via_html ( self , mock_get ) :
mock_get . side_effect = [ HTML_WITH_AS2 , AS2 ]
2023-04-03 14:53:15 +00:00
obj = Object ( id = ' http://orig ' )
ActivityPub . fetch ( obj )
self . assertEqual ( AS2_OBJ , obj . as2 )
2023-04-02 02:13:51 +00:00
2023-03-08 21:10:41 +00:00
mock_get . assert_has_calls ( (
self . as2_req ( ' http://orig ' ) ,
2023-06-20 18:22:54 +00:00
self . as2_req ( ' http://as2 ' , headers = as2 . CONNEG_HEADERS ) ,
2023-03-08 21:10:41 +00:00
) )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-03-08 21:10:41 +00:00
def test_fetch_only_html ( self , mock_get ) :
mock_get . return_value = HTML
2023-07-14 19:45:47 +00:00
obj = Object ( id = ' http://orig ' )
self . assertFalse ( ActivityPub . fetch ( obj ) )
self . assertIsNone ( obj . as1 )
2023-03-08 21:10:41 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-03-08 21:10:41 +00:00
def test_fetch_not_acceptable ( self , mock_get ) :
2023-06-20 18:22:54 +00:00
mock_get . return_value = NOT_ACCEPTABLE
2023-07-14 19:45:47 +00:00
obj = Object ( id = ' http://orig ' )
self . assertFalse ( ActivityPub . fetch ( obj ) )
self . assertIsNone ( obj . as1 )
2023-03-08 21:10:41 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-03-08 21:10:41 +00:00
def test_fetch_ssl_error ( self , mock_get ) :
mock_get . side_effect = requests . exceptions . SSLError
with self . assertRaises ( BadGateway ) :
2023-04-03 14:53:15 +00:00
ActivityPub . fetch ( Object ( id = ' http://orig ' ) )
2023-03-08 21:10:41 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-03-23 03:49:28 +00:00
def test_fetch_no_content ( self , mock_get ) :
2023-03-08 21:10:41 +00:00
mock_get . return_value = self . as2_resp ( ' ' )
with self . assertRaises ( BadGateway ) :
2023-04-03 14:53:15 +00:00
ActivityPub . fetch ( Object ( id = ' http://the/id ' ) )
2023-03-08 21:10:41 +00:00
mock_get . assert_has_calls ( [ self . as2_req ( ' http://the/id ' ) ] )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-03-23 03:49:28 +00:00
def test_fetch_not_json ( self , mock_get ) :
2023-03-08 21:10:41 +00:00
mock_get . return_value = self . as2_resp ( ' XYZ not JSON ' )
with self . assertRaises ( BadGateway ) :
2023-04-03 14:53:15 +00:00
ActivityPub . fetch ( Object ( id = ' http://the/id ' ) )
2023-03-08 21:10:41 +00:00
mock_get . assert_has_calls ( [ self . as2_req ( ' http://the/id ' ) ] )
2023-04-01 19:46:31 +00:00
2023-07-14 19:45:47 +00:00
def test_fetch_non_url ( self ) :
obj = Object ( id = ' x y z ' )
self . assertFalse ( ActivityPub . fetch ( obj ) )
self . assertIsNone ( obj . as1 )
2023-10-24 23:09:28 +00:00
def test_convert ( self ) :
2023-10-25 20:23:11 +00:00
obj = Object ( )
self . assertEqual ( { } , ActivityPub . convert ( obj ) )
obj . our_as1 = { }
self . assertEqual ( { } , ActivityPub . convert ( obj ) )
2023-11-03 22:11:21 +00:00
obj . as2 = { ' baz ' : ' biff ' }
2024-01-29 02:15:37 +00:00
self . assert_equals ( { ' baz ' : ' biff ' } , ActivityPub . convert ( obj ) )
2023-11-03 22:11:21 +00:00
# prevent HTTP fetch to infer protocol
self . store_object ( id = ' https://mas.to/thing ' , source_protocol = ' activitypub ' )
obj . as2 = None
obj . our_as1 = {
' id ' : ' fake:like ' ,
2023-10-25 20:23:11 +00:00
' objectType ' : ' activity ' ,
' verb ' : ' like ' ,
2023-11-03 22:11:21 +00:00
' actor ' : ' fake:user ' ,
' object ' : ' https://mas.to/thing ' ,
}
2023-10-25 20:23:11 +00:00
self . assertEqual ( {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
2023-11-03 22:11:21 +00:00
' id ' : ' https://fa.brid.gy/convert/ap/fake:like ' ,
2023-10-25 20:23:11 +00:00
' type ' : ' Like ' ,
2023-11-03 22:11:21 +00:00
' actor ' : ' https://fa.brid.gy/ap/fake:user ' ,
' object ' : ' https://mas.to/thing ' ,
2023-10-26 19:02:57 +00:00
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
2023-10-25 20:23:11 +00:00
} , ActivityPub . convert ( obj ) )
2023-12-14 23:48:02 +00:00
def test_convert_actor_as2 ( self ) :
self . assert_equals ( ACTOR , ActivityPub . convert ( Object ( as2 = ACTOR ) ) )
2024-03-13 04:09:34 +00:00
@patch ( ' requests.get ' )
def test_convert_actor_as1_from_user ( self , mock_get ) :
mock_get . return_value = requests_response ( test_web . ACTOR_HTML )
2023-12-14 23:48:02 +00:00
obj = Object ( our_as1 = {
' objectType ' : ' person ' ,
' id ' : ' https://user.com/ ' ,
} )
self . assert_equals ( ACTOR_BASE , ActivityPub . convert ( obj , from_user = self . user ) ,
ignore = [ ' endpoints ' , ' followers ' , ' following ' ] )
def test_convert_actor_as1_no_from_user ( self ) :
obj = Object ( our_as1 = ACTOR_AS1 )
self . assert_equals ( ACTOR , common . unwrap ( ActivityPub . convert ( obj ) ) ,
ignore = [ ' to ' , ' attachment ' ] )
def test_convert_follow_as1_no_from_user ( self ) :
2024-04-02 06:03:23 +00:00
# prevent HTTP fetches to infer protocol
self . store_object ( id = ' https://mas.to/6d1a ' , source_protocol = ' activitypub ' )
self . store_object ( id = ' https://user.com/ ' , source_protocol = ' web ' )
2023-12-14 23:48:02 +00:00
obj = Object ( our_as1 = as2 . to_as1 ( FOLLOW ) )
self . assert_equals ( FOLLOW , common . unwrap ( ActivityPub . convert ( obj ) ) ,
ignore = [ ' to ' ] )
def test_convert_profile_update_as1_no_from_user ( self ) :
obj = Object ( our_as1 = {
' objectType ' : ' activity ' ,
' verb ' : ' update ' ,
' object ' : ACTOR_AS1 ,
} )
self . assert_equals ( {
' type ' : ' Update ' ,
' object ' : ACTOR ,
} , common . unwrap ( ActivityPub . convert ( obj ) ) , ignore = [ ' to ' , ' attachment ' ] )
2023-11-24 16:34:39 +00:00
def test_convert_compact_actor_attributedTo_author ( self ) :
obj = Object ( our_as1 = {
' actor ' : { ' id ' : ' baj ' } ,
' author ' : [ { ' id ' : ' bar ' } ] ,
' object ' : { ' author ' : { ' id ' : ' biff ' } } ,
} )
self . assert_equals ( {
' actor ' : ' baj ' ,
' attributedTo ' : ' bar ' ,
' object ' : { ' attributedTo ' : ' biff ' } ,
} , ActivityPub . convert ( obj ) , ignore = [ ' to ' ] )
2024-01-29 02:15:37 +00:00
def test_convert_adds_context_to_as2 ( self ) :
obj = Object ( as2 = {
' type ' : ' Update ' ,
' object ' : ACTOR ,
} )
# use assertEquals so that we don't ignore @context
self . assertEqual ( {
' @context ' : [ as2 . CONTEXT , activitypub . SECURITY_CONTEXT ] ,
' type ' : ' Update ' ,
' object ' : ACTOR ,
} , ActivityPub . convert ( obj ) )
2024-04-14 21:57:00 +00:00
# TODO: remove
@skip
2024-02-28 18:57:30 +00:00
def test_convert_protocols_not_enabled ( self ) :
obj = Object ( our_as1 = { ' foo ' : ' bar ' } , source_protocol = ' atproto ' )
with self . assertRaises ( BadRequest ) :
ActivityPub . convert ( obj )
2023-04-01 19:46:31 +00:00
def test_postprocess_as2_idempotent ( self ) :
for obj in ( ACTOR , REPLY_OBJECT , REPLY_OBJECT_WRAPPED , REPLY ,
NOTE_OBJECT , NOTE , MENTION_OBJECT , MENTION , LIKE ,
2023-04-02 02:13:51 +00:00
LIKE_WRAPPED , REPOST , FOLLOW , FOLLOW_WRAPPED , ACCEPT ,
UNDO_FOLLOW_WRAPPED , DELETE , UPDATE_NOTE ,
2023-11-15 22:23:08 +00:00
LIKE_WITH_ACTOR , REPOST_FULL , FOLLOW_WITH_ACTOR ,
FOLLOW_WRAPPED_WITH_ACTOR , FOLLOW_WITH_OBJECT , UPDATE_PERSON ,
2023-04-01 19:46:31 +00:00
) :
with self . subTest ( obj = obj ) :
obj = copy . deepcopy ( obj )
2023-06-16 20:16:17 +00:00
self . assert_equals ( postprocess_as2 ( obj ) ,
postprocess_as2 ( postprocess_as2 ( obj ) ) ,
ignore = [ ' to ' ] )
2023-06-01 01:34:33 +00:00
2023-11-30 05:06:55 +00:00
def test_handle_as ( self ) :
2023-09-07 00:32:35 +00:00
user = ActivityPub ( obj = Object ( id = ' a ' , as2 = {
* * ACTOR ,
' preferredUsername ' : ' me ' ,
} ) )
2023-11-30 05:06:55 +00:00
self . assertEqual ( ' @me@mas.to ' , user . handle_as ( ActivityPub ) )
2023-09-25 22:08:14 +00:00
self . assertEqual ( ' @me@mas.to ' , user . handle )
2023-06-02 03:58:42 +00:00
2023-06-16 04:22:20 +00:00
user . obj . as2 = ACTOR
2023-11-30 05:06:55 +00:00
self . assertEqual ( ' @swentel@mas.to ' , user . handle_as ( ActivityPub ) )
2023-09-25 22:08:14 +00:00
self . assertEqual ( ' @swentel@mas.to ' , user . handle )
2023-06-02 03:58:42 +00:00
user = ActivityPub ( id = ' https://mas.to/users/alice ' )
2023-11-30 05:06:55 +00:00
self . assertEqual ( ' @alice@mas.to ' , user . handle_as ( ActivityPub ) )
2023-09-25 22:08:14 +00:00
self . assertEqual ( ' @alice@mas.to ' , user . handle )
2023-06-01 01:34:33 +00:00
2023-09-07 00:32:35 +00:00
user = self . make_user ( ' http://a ' , cls = ActivityPub , obj_as2 = {
' id ' : ' https://mas.to/users/foo ' ,
' preferredUsername ' : ' me ' ,
} )
2023-09-25 17:57:16 +00:00
self . assertEqual ( ' me.mas.to.ap.brid.gy ' , user . handle_as ( ' atproto ' ) )
2023-09-07 00:32:35 +00:00
2024-04-02 06:06:25 +00:00
def test_web_url_composite_url_object ( self ) :
actor_as2 = {
' type ' : ' Person ' ,
' url ' : ' https://techhub.social/@foo ' ,
' attachment ' : [ {
' type ' : ' PropertyValue ' ,
' name ' : ' Twitter ' ,
' value ' : ' <span class= " h-card " ><a href= " https://techhub.social/@foo " class= " u-url mention " >@<span>foo</span></a></span> ' ,
} ] ,
}
user = self . make_user ( ' http://foo/actor ' , cls = ActivityPub , obj_as2 = actor_as2 )
self . assertEqual ( ' https://techhub.social/@foo ' , user . web_url ( ) )
2023-06-01 01:34:33 +00:00
def test_web_url ( self ) :
user = self . make_user ( ' http://foo/actor ' , cls = ActivityPub )
self . assertEqual ( ' http://foo/actor ' , user . web_url ( ) )
2023-07-10 19:23:00 +00:00
user . obj = Object ( id = ' a ' , as2 = copy . deepcopy ( ACTOR ) ) # no url
2023-06-01 01:34:33 +00:00
self . assertEqual ( ' http://foo/actor ' , user . web_url ( ) )
2023-06-16 04:22:20 +00:00
user . obj . as2 [ ' url ' ] = [ ' http://my/url ' ]
2023-06-01 01:34:33 +00:00
self . assertEqual ( ' http://my/url ' , user . web_url ( ) )
2023-06-02 04:37:58 +00:00
2023-09-25 22:08:14 +00:00
def test_handle ( self ) :
2024-01-13 03:52:49 +00:00
user = self . make_user ( ' http://foo/ey ' , cls = ActivityPub )
2023-09-25 22:08:14 +00:00
self . assertIsNone ( user . handle )
2024-01-13 03:52:49 +00:00
self . assertEqual ( ' http://foo/ey ' , user . handle_or_id ( ) )
2023-06-02 04:37:58 +00:00
2023-06-16 04:22:20 +00:00
user . obj = Object ( id = ' a ' , as2 = ACTOR )
2023-09-25 22:08:14 +00:00
self . assertEqual ( ' @swentel@mas.to ' , user . handle )
2023-09-25 19:33:24 +00:00
self . assertEqual ( ' @swentel@mas.to ' , user . handle_or_id ( ) )
2023-06-16 20:16:17 +00:00
2023-06-21 14:22:03 +00:00
@skip
2023-06-21 00:06:32 +00:00
def test_target_for_not_activitypub ( self ) :
2023-06-16 20:16:17 +00:00
with self . assertRaises ( AssertionError ) :
ActivityPub . target_for ( Object ( source_protocol = ' web ' ) )
2023-06-21 00:06:32 +00:00
def test_target_for_actor ( self ) :
2023-06-16 20:16:17 +00:00
self . assertEqual ( ACTOR [ ' inbox ' ] , ActivityPub . target_for (
Object ( source_protocol = ' ap ' , as2 = ACTOR ) ) )
actor = copy . deepcopy ( ACTOR )
del actor [ ' inbox ' ]
self . assertIsNone ( ActivityPub . target_for (
Object ( source_protocol = ' ap ' , as2 = actor ) ) )
actor [ ' publicInbox ' ] = ' so-public '
self . assertEqual ( ' so-public ' , ActivityPub . target_for (
Object ( source_protocol = ' ap ' , as2 = actor ) ) )
# sharedInbox
self . assertEqual ( ' so-public ' , ActivityPub . target_for (
Object ( source_protocol = ' ap ' , as2 = actor ) , shared = True ) )
actor [ ' endpoints ' ] = {
' sharedInbox ' : ' so-shared ' ,
}
self . assertEqual ( ' so-public ' , ActivityPub . target_for (
Object ( source_protocol = ' ap ' , as2 = actor ) ) )
self . assertEqual ( ' so-shared ' , ActivityPub . target_for (
Object ( source_protocol = ' ap ' , as2 = actor ) , shared = True ) )
2023-06-21 00:06:32 +00:00
def test_target_for_object ( self ) :
obj = Object ( as2 = NOTE_OBJECT , source_protocol = ' ap ' )
self . assertIsNone ( ActivityPub . target_for ( obj ) )
Object ( id = ACTOR [ ' id ' ] , as2 = ACTOR ) . put ( )
obj . as2 = {
* * NOTE_OBJECT ,
' author ' : ACTOR [ ' id ' ] ,
}
self . assertEqual ( ' http://mas.to/inbox ' , ActivityPub . target_for ( obj ) )
del obj . as2 [ ' author ' ]
obj . as2 [ ' actor ' ] = copy . deepcopy ( ACTOR )
obj . as2 [ ' actor ' ] [ ' url ' ] = [ obj . as2 [ ' actor ' ] . pop ( ' id ' ) ]
self . assertEqual ( ' http://mas.to/inbox ' , ActivityPub . target_for ( obj ) )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-06-21 00:06:32 +00:00
def test_target_for_object_fetch ( self , mock_get ) :
mock_get . return_value = self . as2_resp ( ACTOR )
obj = Object ( as2 = {
* * NOTE_OBJECT ,
' author ' : ' http://the/author ' ,
} , source_protocol = ' ap ' )
self . assertEqual ( ' http://mas.to/inbox ' , ActivityPub . target_for ( obj ) )
mock_get . assert_has_calls ( [ self . as2_req ( ' http://the/author ' ) ] )
2023-07-23 06:02:12 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.get ' )
2023-07-27 22:04:15 +00:00
def test_target_for_author_is_object_id ( self , mock_get ) :
2023-11-03 22:11:21 +00:00
mock_get . return_value = HTML
2023-07-27 22:04:15 +00:00
obj = self . store_object ( id = ' http://the/author ' , our_as1 = {
' author ' : ' http://the/author ' ,
} )
# test is that we short circuit out instead of infinite recursion
self . assertIsNone ( ActivityPub . target_for ( obj ) )
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.post ' )
2023-07-23 06:02:12 +00:00
def test_send_blocklisted ( self , mock_post ) :
self . assertFalse ( ActivityPub . send ( Object ( as2 = NOTE ) ,
' https://fed.brid.gy/ap/sharedInbox ' ) )
mock_post . assert_not_called ( )
2023-11-03 22:11:21 +00:00
Revert "cache outbound HTTP request responses, locally to each inbound request"
This reverts commit 30debfc8faf730190bd51a3aef49df6c6bfbd50a.
seemed promising, but broke in production. Saw a lot of `IncompleteRead`s on both GETs and POSTs. Rolled back for now.
```
('Connection broken: IncompleteRead(9172 bytes read, -4586 more expected)', IncompleteRead(9172 bytes read, -4586 more expected))
...
File "oauth_dropins/webutil/util.py", line 1673, in call
resp = getattr((session or requests), fn)(url, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 102, in get
return self.request('GET', url, params=params, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 158, in request
return super().request(method, url, *args, headers=headers, **kwargs) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/sessions.py", line 589, in request
resp = self.send(prep, **send_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 205, in send
response = self._send_and_cache(request, actions, cached_response, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/session.py", line 233, in _send_and_cache
self.cache.save_response(response, actions.cache_key, actions.expires)
File "requests_cache/backends/base.py", line 89, in save_response
cached_response = CachedResponse.from_response(response, expires=expires)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/response.py", line 102, in from_response
obj.raw = CachedHTTPResponse.from_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests_cache/models/raw_response.py", line 69, in from_response
_ = response.content # This property reads, decodes, and stores response content
^^^^^^^^^^^^^^^^
File "requests/models.py", line 899, in content
self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "requests/models.py", line 818, in generate
raise ChunkedEncodingError(e)
```
2024-03-08 21:24:28 +00:00
@patch ( ' requests.post ' )
2023-11-03 22:11:21 +00:00
def test_send_convert_ids ( self , mock_post ) :
mock_post . return_value = requests_response ( )
like = Object ( our_as1 = {
' id ' : ' fake:like ' ,
' objectType ' : ' activity ' ,
' verb ' : ' like ' ,
' object ' : ' fake:post ' ,
' actor ' : ' fake:user ' ,
} )
2023-11-26 04:07:14 +00:00
self . assertTrue ( ActivityPub . send ( like , ' https://inbox ' , from_user = self . user ) )
2023-11-03 22:11:21 +00:00
self . assertEqual ( 1 , len ( mock_post . call_args_list ) )
args , kwargs = mock_post . call_args_list [ 0 ]
self . assertEqual ( ( ' https://inbox ' , ) , args )
self . assertEqual ( {
' @context ' : ' https://www.w3.org/ns/activitystreams ' ,
' id ' : ' https://fa.brid.gy/convert/ap/fake:like ' ,
' type ' : ' Like ' ,
' object ' : ' https://fa.brid.gy/convert/ap/fake:post ' ,
' actor ' : ' https://fa.brid.gy/ap/fake:user ' ,
' to ' : [ as2 . PUBLIC_AUDIENCE ] ,
} , json_loads ( kwargs [ ' data ' ] ) )