Activity Pub Implementation #58
|
@ -1,10 +1,12 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -17,6 +19,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
vocab "github.com/go-ap/activitypub"
|
vocab "github.com/go-ap/activitypub"
|
||||||
|
"github.com/go-ap/jsonld"
|
||||||
"github.com/go-fed/httpsig"
|
"github.com/go-fed/httpsig"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,7 +45,10 @@ func (cfg *ActivityPubConfig) ParseFormData(data model.HttpFormData, binSvc mode
|
||||||
|
|
||||||
func (cfg *ActivityPubConfig) PrivateKey() *rsa.PrivateKey {
|
func (cfg *ActivityPubConfig) PrivateKey() *rsa.PrivateKey {
|
||||||
block, _ := pem.Decode([]byte(cfg.PrivateKeyPem))
|
block, _ := pem.Decode([]byte(cfg.PrivateKeyPem))
|
||||||
privKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
|
privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error x509.ParsePKCS1PrivateKey", "err", err)
|
||||||
|
}
|
||||||
return privKey
|
return privKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +137,13 @@ func (s *ActivityPubService) sign(privateKey *rsa.PrivateKey, pubKeyId string, b
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ActivityPubService) GetActor(reqUrl string, fromGame string) (vocab.Actor, error) {
|
func (s *ActivityPubService) GetActor(reqUrl string) (vocab.Actor, error) {
|
||||||
|
|
||||||
|
siteConfig := model.SiteConfig{}
|
||||||
|
apConfig := ActivityPubConfig{}
|
||||||
|
s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig)
|
||||||
|
s.configRepo.Get(config.SITE_CONFIG, &siteConfig)
|
||||||
|
|
||||||
c := http.Client{}
|
c := http.Client{}
|
||||||
|
|
||||||
parsedUrl, err := url.Parse(reqUrl)
|
parsedUrl, err := url.Parse(reqUrl)
|
||||||
|
@ -145,11 +157,6 @@ func (s *ActivityPubService) GetActor(reqUrl string, fromGame string) (vocab.Act
|
||||||
req.Header.Set("Date", time.Now().Format(http.TimeFormat))
|
req.Header.Set("Date", time.Now().Format(http.TimeFormat))
|
||||||
req.Header.Set("Host", parsedUrl.Host)
|
req.Header.Set("Host", parsedUrl.Host)
|
||||||
|
|
||||||
siteConfig := model.SiteConfig{}
|
|
||||||
apConfig := ActivityPubConfig{}
|
|
||||||
s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig)
|
|
||||||
s.configRepo.Get(config.SITE_CONFIG, &siteConfig)
|
|
||||||
|
|
||||||
err = s.sign(apConfig.PrivateKey(), siteConfig.FullUrl+"/activitypub/actor#main-key", nil, req)
|
err = s.sign(apConfig.PrivateKey(), siteConfig.FullUrl+"/activitypub/actor#main-key", nil, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Signing error", "err", err)
|
slog.Error("Signing error", "err", err)
|
||||||
|
@ -188,7 +195,9 @@ func (s *ActivityPubService) VerifySignature(r *http.Request, sender string) err
|
||||||
s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig)
|
s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig)
|
||||||
s.configRepo.Get(config.SITE_CONFIG, &siteConfig)
|
s.configRepo.Get(config.SITE_CONFIG, &siteConfig)
|
||||||
|
|
||||||
actor, err := s.GetActor(sender, siteConfig.FullUrl+"/activitypub/actor")
|
slog.Info("verifying for", "sender", sender, "retriever", siteConfig.FullUrl+"/activitypub/actor")
|
||||||
|
|
||||||
|
actor, err := s.GetActor(sender)
|
||||||
// actor does not have a pub key -> don't verify
|
// actor does not have a pub key -> don't verify
|
||||||
if actor.PublicKey.PublicKeyPem == "" {
|
if actor.PublicKey.PublicKeyPem == "" {
|
||||||
return nil
|
return nil
|
||||||
|
@ -213,3 +222,67 @@ func (s *ActivityPubService) VerifySignature(r *http.Request, sender string) err
|
||||||
}
|
}
|
||||||
return verifier.Verify(pubKey, httpsig.RSA_SHA256)
|
return verifier.Verify(pubKey, httpsig.RSA_SHA256)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ActivityPubService) Accept(act *vocab.Activity) error {
|
||||||
|
actor, err := s.GetActor(act.Actor.GetID().String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accept := vocab.AcceptNew(vocab.IRI("TODO"), act)
|
||||||
|
data, err := jsonld.WithContext(
|
||||||
|
jsonld.IRI(vocab.ActivityBaseURI),
|
||||||
|
).Marshal(accept)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("marshalling error", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.sendObject(actor, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActivityPubService) sendObject(to vocab.Actor, data []byte) error {
|
||||||
|
siteConfig := model.SiteConfig{}
|
||||||
|
apConfig := ActivityPubConfig{}
|
||||||
|
s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig)
|
||||||
|
s.configRepo.Get(config.SITE_CONFIG, &siteConfig)
|
||||||
|
|
||||||
|
if to.Inbox == nil {
|
||||||
|
slog.Error("actor has no inbox", "actor", to)
|
||||||
|
return errors.New("actor has no inbox")
|
||||||
|
}
|
||||||
|
|
||||||
|
actorUrl, err := url.Parse(to.Inbox.GetID().String())
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("parse error", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := http.Client{}
|
||||||
|
req, _ := http.NewRequest("POST", to.Inbox.GetID().String(), bytes.NewReader(data))
|
||||||
|
req.Header.Set("Accept", "application/ld+json")
|
||||||
|
req.Header.Set("Date", time.Now().Format(http.TimeFormat))
|
||||||
|
req.Header.Set("Host", actorUrl.Host)
|
||||||
|
err = s.sign(apConfig.PrivateKey(), siteConfig.FullUrl+"/activitypub/actor#main-key", data, req)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Signing error", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := c.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Sending error", "url", req.URL, "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slog.Info("Request", "host", resp.Request.Header)
|
||||||
|
|
||||||
|
if resp.StatusCode > 299 {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
slog.Error("Error sending Note", "method", resp.Request.Method, "url", resp.Request.URL, "status", resp.Status, "body", string(body))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
slog.Info("Sent Body", "body", string(data))
|
||||||
|
slog.Info("Retrieved", "status", resp.Status, "body", string(body))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -52,23 +52,23 @@ def webfinger():
|
||||||
{
|
{
|
||||||
"subject": "acct:h4kor@mock_masto",
|
"subject": "acct:h4kor@mock_masto",
|
||||||
"aliases": [
|
"aliases": [
|
||||||
"http://mock_masto/@h4kor",
|
"http://mock_masto:8000/@h4kor",
|
||||||
"http://mock_masto/users/h4kor",
|
"http://mock_masto:8000/users/h4kor",
|
||||||
],
|
],
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"rel": "http://webfinger.net/rel/profile-page",
|
"rel": "http://webfinger.net/rel/profile-page",
|
||||||
"type": "text/html",
|
"type": "text/html",
|
||||||
"href": "http://mock_masto/@h4kor",
|
"href": "http://mock_masto:8000/@h4kor",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rel": "self",
|
"rel": "self",
|
||||||
"type": "application/activity+json",
|
"type": "application/activity+json",
|
||||||
"href": "http://mock_masto/users/h4kor",
|
"href": "http://mock_masto:8000/users/h4kor",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rel": "http://ostatus.org/schema/1.0/subscribe",
|
"rel": "http://ostatus.org/schema/1.0/subscribe",
|
||||||
"template": "http://mock_masto/authorize_interaction?uri={uri}",
|
"template": "http://mock_masto:8000/authorize_interaction?uri={uri}",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rel": "http://webfinger.net/rel/avatar",
|
"rel": "http://webfinger.net/rel/avatar",
|
||||||
|
@ -120,53 +120,53 @@ def actor():
|
||||||
"focalPoint": {"@container": "@list", "@id": "toot:focalPoint"},
|
"focalPoint": {"@container": "@list", "@id": "toot:focalPoint"},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"id": "http://mock_masto/users/h4kor",
|
"id": "http://mock_masto:8000/users/h4kor",
|
||||||
"type": "Person",
|
"type": "Person",
|
||||||
"following": "http://mock_masto/users/h4kor/following",
|
"following": "http://mock_masto:8000/users/h4kor/following",
|
||||||
"followers": "http://mock_masto/users/h4kor/followers",
|
"followers": "http://mock_masto:8000/users/h4kor/followers",
|
||||||
"inbox": "http://mock_masto/users/h4kor/inbox",
|
"inbox": "http://mock_masto:8000/users/h4kor/inbox",
|
||||||
"outbox": "http://mock_masto/users/h4kor/outbox",
|
"outbox": "http://mock_masto:8000/users/h4kor/outbox",
|
||||||
"featured": "http://mock_masto/users/h4kor/collections/featured",
|
"featured": "http://mock_masto:8000/users/h4kor/collections/featured",
|
||||||
"featuredTags": "http://mock_masto/users/h4kor/collections/tags",
|
"featuredTags": "http://mock_masto:8000/users/h4kor/collections/tags",
|
||||||
"preferredUsername": "h4kor",
|
"preferredUsername": "h4kor",
|
||||||
"name": "Niko",
|
"name": "Niko",
|
||||||
"summary": '<p>Teaching computers to do things with arguable efficiency.</p><p>he/him</p><p><a href="http://mock_masto/tags/vegan" class="mention hashtag" rel="tag">#<span>vegan</span></a> <a href="http://mock_masto/tags/cooking" class="mention hashtag" rel="tag">#<span>cooking</span></a> <a href="http://mock_masto/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="http://mock_masto/tags/politics" class="mention hashtag" rel="tag">#<span>politics</span></a> <a href="http://mock_masto/tags/climate" class="mention hashtag" rel="tag">#<span>climate</span></a></p>',
|
"summary": '<p>Teaching computers to do things with arguable efficiency.</p><p>he/him</p><p><a href="http://mock_masto:8000/tags/vegan" class="mention hashtag" rel="tag">#<span>vegan</span></a> <a href="http://mock_masto:8000/tags/cooking" class="mention hashtag" rel="tag">#<span>cooking</span></a> <a href="http://mock_masto:8000/tags/programming" class="mention hashtag" rel="tag">#<span>programming</span></a> <a href="http://mock_masto:8000/tags/politics" class="mention hashtag" rel="tag">#<span>politics</span></a> <a href="http://mock_masto:8000/tags/climate" class="mention hashtag" rel="tag">#<span>climate</span></a></p>',
|
||||||
"url": "http://mock_masto/@h4kor",
|
"url": "http://mock_masto:8000/@h4kor",
|
||||||
"manuallyApprovesFollowers": False,
|
"manuallyApprovesFollowers": False,
|
||||||
"discoverable": True,
|
"discoverable": True,
|
||||||
"indexable": False,
|
"indexable": False,
|
||||||
"published": "2018-08-16T00:00:00Z",
|
"published": "2018-08-16T00:00:00Z",
|
||||||
"memorial": False,
|
"memorial": False,
|
||||||
"devices": "http://mock_masto/users/h4kor/collections/devices",
|
"devices": "http://mock_masto:8000/users/h4kor/collections/devices",
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
"id": "http://mock_masto/users/h4kor#main-key",
|
"id": "http://mock_masto:8000/users/h4kor#main-key",
|
||||||
"owner": "http://mock_masto/users/h4kor",
|
"owner": "http://mock_masto:8000/users/h4kor",
|
||||||
"publicKeyPem": PUB_KEY_PEM,
|
"publicKeyPem": PUB_KEY_PEM,
|
||||||
},
|
},
|
||||||
"tag": [
|
"tag": [
|
||||||
{
|
{
|
||||||
"type": "Hashtag",
|
"type": "Hashtag",
|
||||||
"href": "http://mock_masto/tags/politics",
|
"href": "http://mock_masto:8000/tags/politics",
|
||||||
"name": "#politics",
|
"name": "#politics",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Hashtag",
|
"type": "Hashtag",
|
||||||
"href": "http://mock_masto/tags/climate",
|
"href": "http://mock_masto:8000/tags/climate",
|
||||||
"name": "#climate",
|
"name": "#climate",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Hashtag",
|
"type": "Hashtag",
|
||||||
"href": "http://mock_masto/tags/vegan",
|
"href": "http://mock_masto:8000/tags/vegan",
|
||||||
"name": "#vegan",
|
"name": "#vegan",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Hashtag",
|
"type": "Hashtag",
|
||||||
"href": "http://mock_masto/tags/programming",
|
"href": "http://mock_masto:8000/tags/programming",
|
||||||
"name": "#programming",
|
"name": "#programming",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Hashtag",
|
"type": "Hashtag",
|
||||||
"href": "http://mock_masto/tags/cooking",
|
"href": "http://mock_masto:8000/tags/cooking",
|
||||||
"name": "#cooking",
|
"name": "#cooking",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -188,7 +188,7 @@ def actor():
|
||||||
"value": '<a href="http://git.libove.org/h4kor/owl-blogs" target="_blank" rel="nofollow noopener noreferrer me" translate="no"><span class="invisible">http://</span><span class="">git.libove.org/h4kor/owl-blogs</span><span class="invisible"></span></a>',
|
"value": '<a href="http://git.libove.org/h4kor/owl-blogs" target="_blank" rel="nofollow noopener noreferrer me" translate="no"><span class="invisible">http://</span><span class="">git.libove.org/h4kor/owl-blogs</span><span class="invisible"></span></a>',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"endpoints": {"sharedInbox": "http://mock_masto/inbox"},
|
"endpoints": {"sharedInbox": "http://mock_masto:8000/inbox"},
|
||||||
"icon": {
|
"icon": {
|
||||||
"type": "Image",
|
"type": "Image",
|
||||||
"mediaType": "image/png",
|
"mediaType": "image/png",
|
||||||
|
@ -198,12 +198,17 @@ def actor():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/users/h4kor/inbox")
|
@app.route("/users/h4kor/inbox", methods=["POST"])
|
||||||
def inbox():
|
def inbox():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
INBOX.append(request.get_json())
|
INBOX.append(json.loads(request.get_data()))
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/msgs")
|
||||||
|
def msgs():
|
||||||
|
return json.dumps(INBOX)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(debug=True, host="0.0.0.0", port="8000")
|
app.run(debug=True, host="0.0.0.0", port="8000")
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
|
from contextlib import contextmanager
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
import json
|
import json
|
||||||
|
from time import sleep
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
import requests, base64, hashlib
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
|
||||||
ACCT_NAME = "acct:blog@localhost:3000"
|
ACCT_NAME = "acct:blog@localhost:3000"
|
||||||
|
|
||||||
|
@ -36,49 +42,23 @@ K98rXSX3VvY4w48AznvPMKVLqesFjcvwnBdvk/NqXod20CMSpOEVj6W/nGoTBQt2
|
||||||
|
|
||||||
|
|
||||||
def ensure_follow(client, inbox_url, actor_url):
|
def ensure_follow(client, inbox_url, actor_url):
|
||||||
resp = client.post(
|
req = sign(
|
||||||
|
"POST",
|
||||||
inbox_url,
|
inbox_url,
|
||||||
json={
|
{
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
"id": "https://mock_masto/d0b5768b-a15b-4ed6-bc84-84c7e2b57588",
|
"id": "http://mock_masto:8000/d0b5768b-a15b-4ed6-bc84-84c7e2b57588",
|
||||||
"type": "Follow",
|
"type": "Follow",
|
||||||
"actor": "http://mock_masto:8000/users/h4kor",
|
"actor": "http://mock_masto:8000/users/h4kor",
|
||||||
"object": actor_url,
|
"object": actor_url,
|
||||||
},
|
},
|
||||||
headers={"Content-Type": "application/activity+json"},
|
|
||||||
)
|
)
|
||||||
|
resp = requests.Session().send(req)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def get_gmt_now() -> str:
|
|
||||||
return datetime.now(datetime.UTC).strftime("%a, %d %b %Y %H:%M:%S GMT")
|
|
||||||
|
|
||||||
|
|
||||||
from http_message_signatures import (
|
|
||||||
HTTPMessageSigner,
|
|
||||||
HTTPMessageVerifier,
|
|
||||||
HTTPSignatureKeyResolver,
|
|
||||||
algorithms,
|
|
||||||
)
|
|
||||||
import requests, base64, hashlib, http_sfv
|
|
||||||
|
|
||||||
|
|
||||||
class MyHTTPSignatureKeyResolver(HTTPSignatureKeyResolver):
|
|
||||||
keys = {"my-key": b"top-secret-key"}
|
|
||||||
|
|
||||||
def resolve_public_key(self, key_id: str):
|
|
||||||
return self.keys[key_id]
|
|
||||||
|
|
||||||
def resolve_private_key(self, key_id: str):
|
|
||||||
return priv_key
|
|
||||||
# return PRIV_KEY_PEM
|
|
||||||
|
|
||||||
|
|
||||||
def sign(method, url, data):
|
def sign(method, url, data):
|
||||||
from urllib.parse import urlparse
|
|
||||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
|
||||||
from cryptography.hazmat.primitives import hashes
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
|
||||||
|
|
||||||
priv_key = load_pem_private_key(PRIV_KEY_PEM.encode(), None)
|
priv_key = load_pem_private_key(PRIV_KEY_PEM.encode(), None)
|
||||||
body = json.dumps(data).encode()
|
body = json.dumps(data).encode()
|
||||||
|
@ -103,6 +83,20 @@ date: {date}""".encode()
|
||||||
request.headers["Host"] = host
|
request.headers["Host"] = host
|
||||||
request.headers["Date"] = date
|
request.headers["Date"] = date
|
||||||
request.headers["Signature"] = (
|
request.headers["Signature"] = (
|
||||||
f'keyId="http://mock_masto/users/h4kor#main-key",headers="(request-target) host date",signature="{sig_str}"'
|
f'keyId="http://mock_masto:8000/users/h4kor#main-key",headers="(request-target) host date",signature="{sig_str}"'
|
||||||
)
|
)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def msg_inc(n):
|
||||||
|
resp = requests.get("http://localhost:8000/msgs")
|
||||||
|
data = resp.json()
|
||||||
|
msgs = len(data)
|
||||||
|
yield
|
||||||
|
sleep(0.2)
|
||||||
|
resp = requests.get("http://localhost:8000/msgs")
|
||||||
|
data = resp.json()
|
||||||
|
assert msgs + n == len(
|
||||||
|
data
|
||||||
|
), f"prev: {msgs}, now: {len(data)}, expected: {msgs + n}"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
from time import sleep
|
||||||
import requests
|
import requests
|
||||||
from .fixtures import ensure_follow, sign
|
from .fixtures import ensure_follow, msg_inc, sign
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,57 +29,59 @@ def test_actor(client, actor_url):
|
||||||
|
|
||||||
|
|
||||||
def test_following(client, inbox_url, followers_url, actor_url):
|
def test_following(client, inbox_url, followers_url, actor_url):
|
||||||
req = sign(
|
with msg_inc(1):
|
||||||
"POST",
|
req = sign(
|
||||||
inbox_url,
|
"POST",
|
||||||
{
|
inbox_url,
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
{
|
||||||
"id": "https://mock_masto/d0b5768b-a15b-4ed6-bc84-84c7e2b57588",
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
"type": "Follow",
|
"id": "http://mock_masto:8000/d0b5768b-a15b-4ed6-bc84-84c7e2b57588",
|
||||||
"actor": "http://mock_masto:8000/users/h4kor",
|
"type": "Follow",
|
||||||
"object": actor_url,
|
"actor": "http://mock_masto:8000/users/h4kor",
|
||||||
},
|
"object": actor_url,
|
||||||
)
|
},
|
||||||
pprint(req.headers)
|
)
|
||||||
resp = requests.Session().send(req)
|
resp = requests.Session().send(req)
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
||||||
resp = client.get(
|
resp = client.get(
|
||||||
followers_url, headers={"Content-Type": "application/activity+json"}
|
followers_url, headers={"Content-Type": "application/activity+json"}
|
||||||
)
|
)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
pprint(data)
|
pprint(data)
|
||||||
assert "items" in data
|
assert "items" in data
|
||||||
assert len(data["items"]) == 1
|
assert len(data["items"]) == 1
|
||||||
|
|
||||||
|
|
||||||
# def test_unfollow(client, inbox_url, followers_url, actor_url):
|
def test_unfollow(client, inbox_url, followers_url, actor_url):
|
||||||
# ensure_follow(client, inbox_url, actor_url)
|
ensure_follow(client, inbox_url, actor_url)
|
||||||
|
with msg_inc(1):
|
||||||
|
req = sign(
|
||||||
|
"POST",
|
||||||
|
inbox_url,
|
||||||
|
{
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"id": "http://mock_masto:8000/users/h4kor#follows/3632040/undo",
|
||||||
|
"type": "Undo",
|
||||||
|
"actor": "http://mock_masto:8000/users/h4kor",
|
||||||
|
"object": {
|
||||||
|
"id": "http://mock_masto:8000/d0b5768b-a15b-4ed6-bc84-84c7e2b57588",
|
||||||
|
"type": "Follow",
|
||||||
|
"actor": "http://mock_masto:8000/users/h4kor",
|
||||||
|
"object": actor_url,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
resp = requests.Session().send(req)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
# resp = client.post(
|
resp = client.get(
|
||||||
# inbox_url,
|
followers_url, headers={"Content-Type": "application/activity+json"}
|
||||||
# json={
|
)
|
||||||
# "@context": "https://www.w3.org/ns/activitystreams",
|
assert resp.status_code == 200
|
||||||
# "id": "http://mock_masto:8000/users/h4kor#follows/3632040/undo",
|
data = resp.json()
|
||||||
# "type": "Undo",
|
pprint(data)
|
||||||
# "actor": "http://mock_masto:8000/users/h4kor",
|
assert "totalItems" in data
|
||||||
# "object": {
|
assert data["totalItems"] == 0
|
||||||
# "id": "https://mock_masto/d0b5768b-a15b-4ed6-bc84-84c7e2b57588",
|
|
||||||
# "type": "Follow",
|
|
||||||
# "actor": "http://mock_masto:8000/users/h4kor",
|
|
||||||
# "object": actor_url,
|
|
||||||
# },
|
|
||||||
# },
|
|
||||||
# headers={"Content-Type": "application/activity+json"},
|
|
||||||
# )
|
|
||||||
# assert resp.status_code == 200
|
|
||||||
|
|
||||||
# resp = client.get(
|
|
||||||
# followers_url, headers={"Content-Type": "application/activity+json"}
|
|
||||||
# )
|
|
||||||
# assert resp.status_code == 200
|
|
||||||
# data = resp.json()
|
|
||||||
# assert "items" in data
|
|
||||||
# assert len(data["items"]) == 0
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ func (s *ActivityPubServer) processFollow(r *http.Request, act *vocab.Activity)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// go acpub.Accept(gameName, act)
|
go s.apService.Accept(act)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue