move fediverse_server to a directory module, add rough outline of toot

federation
This commit is contained in:
jesopo 2019-09-13 11:51:39 +01:00
parent 275c61d8e6
commit b835c109a0
2 changed files with 81 additions and 9 deletions

View file

@ -1,13 +1,16 @@
#--require-config tls-certificate #--require-config tls-certificate
import binascii, os, urllib.parse import base64, binascii, os, urllib.parse
from src import ModuleManager, utils from src import ModuleManager, utils
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
ACTIVITY_TYPE = ("application/ld+json; " LD_TYPE = ("application/ld+json; "
"profile=\"https://www.w3.org/ns/activitystreams\"") "profile=\"https://www.w3.org/ns/activitystreams\"")
WEBFINGER_LINK = "application/activity+json" JRD_TYPE = "application/jrd+json"
WEBFINGER_TYPE = "application/jrd+json" ACTIVITY_TYPE = "application/activity+json"
ACTIVITY_SETTING_PREFIX = "ap-activity-" ACTIVITY_SETTING_PREFIX = "ap-activity-"
@ -56,6 +59,40 @@ class Module(ModuleManager.BaseModule):
activity_id = self._make_activity(event["args"]) activity_id = self._make_activity(event["args"])
event["stdout"].write("Sent toot %s" % activity_id) event["stdout"].write("Sent toot %s" % activity_id)
def _federate_activity(self, activity_id, content, timestamp):
message = {
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Announce",
"to": [],
"actor": "",
"object": ""
}
def _federate(self, data):
our_username, our_instance = self._ap_self()
key_id = self._ap_keyid_url(url_for, our_username)
now = email.utils.formatdate(timeval=None, localtime=False, usegmt=True)
url_for = self.exports.get_one("url-for")
key = security.private_key(self.bot.config["tls-certificate"])
for inbox in self._get_inboxes():
parts = urllib.parse.urlparse(inbox)
headers = [
["host", parts.netloc],
["date", now]
]
sign_headers = headers[:]
sign_headers.insert(0, ["(request-target)", "post %s" % parts.path])
signature = security.signature(key, key_id, sign_headers)
data = ""
request = utils.http.Request(inbox, data=data, headers=headers,
content_type=ACTIVITY_TYPE, useragent="BitBot Fediverse")
utils.http.request()
def _ap_self(self): def _ap_self(self):
our_username = self.bot.get_setting("fediverse", None) our_username = self.bot.get_setting("fediverse", None)
return _parse_username(our_username) return _parse_username(our_username)
@ -70,6 +107,8 @@ class Module(ModuleManager.BaseModule):
return self._ap_url(url_for, "ap-outbox", {"u": our_username}) return self._ap_url(url_for, "ap-outbox", {"u": our_username})
def _ap_activity_url(self, url_for, activity_id): def _ap_activity_url(self, url_for, activity_id):
return self._ap_url(url_for, "ap-activity", {"a": activity_id}) return self._ap_url(url_for, "ap-activity", {"a": activity_id})
def _ap_keyid_url(self, url_for, our_username):
return "%s#key" % self._ap_self_url(url_for, our_username)
@utils.hook("api.get.ap-webfinger") @utils.hook("api.get.ap-webfinger")
@utils.kwarg("authenticated", False) @utils.kwarg("authenticated", False)
@ -88,13 +127,13 @@ class Module(ModuleManager.BaseModule):
self_id = self._ap_self_url(event["url_for"], our_username) self_id = self._ap_self_url(event["url_for"], our_username)
event["response"].content_type = WEBFINGER_TYPE event["response"].content_type = JRD_TYPE
event["response"].write_json({ event["response"].write_json({
"aliases": [self_id], "aliases": [self_id],
"links": [{ "links": [{
"href": self_id, "href": self_id,
"rel": "self", "rel": "self",
"type": WEBFINGER_LINK "type": ACTIVITY_TYPE
}], }],
"subject": "acct:%s" % resource "subject": "acct:%s" % resource
}) })
@ -108,6 +147,7 @@ class Module(ModuleManager.BaseModule):
def ap_user(self, event): def ap_user(self, event):
our_username, our_instance = self._ap_self() our_username, our_instance = self._ap_self()
username = event["params"].get("u", None) username = event["params"].get("u", None)
if username and username == our_username: if username and username == our_username:
self_id = self._ap_self_url(event["url_for"], our_username) self_id = self._ap_self_url(event["url_for"], our_username)
inbox = self._ap_inbox_url(event["url_for"], our_username) inbox = self._ap_inbox_url(event["url_for"], our_username)
@ -117,7 +157,7 @@ class Module(ModuleManager.BaseModule):
with open(cert_filename) as cert_file: with open(cert_filename) as cert_file:
cert = cert_file.read().strip() cert = cert_file.read().strip()
event["response"].content_type = ACTIVITY_TYPE event["response"].content_type = LD_TYPE
event["response"].write_json({ event["response"].write_json({
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
"id": self_id, "url": self_id, "id": self_id, "url": self_id,
@ -168,11 +208,11 @@ class Module(ModuleManager.BaseModule):
"id": activity_url, "id": activity_url,
"object": activity_object, "object": activity_object,
"published": timestamp, "published": timestamp,
"to": ["https://www.w3.org/ns/activitystreams#Public"], "to": "https://www.w3.org/ns/activitystreams#Public",
"type": "Create" "type": "Create"
}) })
event["response"].content_type = ACTIVITY_TYPE event["response"].content_type = LD_TYPE
event["response"].write_json({ event["response"].write_json({
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
"id": outbox, "id": outbox,

View file

@ -0,0 +1,32 @@
import base64, typing
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.backends import default_backend
SIGNATURE_FORMAT = (
"keyId=\"%s\",headers=\"%s\",signature=\"%s\",algorithm=\"rsa-sha256\"")
def private_key(key_filename: str) -> rsa.RSAPrivateKey:
with open(key_filename) as key_file:
return serialization.load_pem_private_key(
key_file.read(), password=None, backend=default_backend())
def signature(key: rsa.RSAPrivateKey, key_id: str,
headers: typing.List[typing.Tuple[str, str]]) -> str:
private_key = _private_key(key_filename)
sign_header_keys = " ".join(h[0] for h in headers)
sign_string_parts = ["%s: %s" % (k, v) for k, v in headers]
sign_string = "\n".join(sign_string_parts)
signature = private_key.sign(
sign_string.encode("utf8"),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256()
)
signature = base64.b64encode(signature).decode("ascii")
return SIGNATURE_FORMAT % (key_id, sign_header_keys, signature)