开源软件名称(OpenSource Name):timmot/activity-pub-tutorial开源软件地址(OpenSource Url):https://github.com/timmot/activity-pub-tutorial开源编程语言(OpenSource Language):开源软件介绍(OpenSource Introduction):ActivityPub TutorialRequirements
Short method
Long methodWe'll run with some assumptions. Your domain name is 1. Read the ActivityPub overviewhttps://www.w3.org/TR/activitypub/#Overview 2. Create an endpoint for yourselfhttps://www.w3.org/TR/activitypub/#actors ActivityStreams expects that we define a @context, id, type, and name property. ActivityPub expects that we define an inbox and outbox property. @app.route('/users/<username>')
def user(username):
if username != "zampano":
abort(404)
response = make_response({
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/zampano",
"inbox": "https://example.com/users/zampano/inbox",
"outbox": "https://example.com/users/zampano/outbox",
"type": "Person",
"name": "Zampano",
})
# Servers may discard the result if you do not set the appropriate content type
response.headers['Content-Type'] = 'application/activity+json'
return response 3. Extend this endpoint for real serversThis would be okay and meets the core specification, but to interact with Mastodon we need to add the preferredUsername attribute (from ActivityPub) and we need to add the publicKey property (from Linked Data Proofs). Generate public and private keysopenssl genrsa -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem or in Python from cryptography.hazmat.primitives import serialization as crypto_serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend as crypto_default_backend
key = rsa.generate_private_key(
backend=crypto_default_backend(),
public_exponent=65537,
key_size=2048
)
private_key = key.private_bytes(
crypto_serialization.Encoding.PEM,
crypto_serialization.PrivateFormat.PKCS8,
crypto_serialization.NoEncryption())
public_key = key.public_key().public_bytes(
crypto_serialization.Encoding.PEM,
crypto_serialization.PublicFormat.SubjectPublicKeyInfo
) Modify user endpoint@app.route('/users/<username>')
def user(username):
if username != "zampano":
abort(404)
public_key = b'' # retrieve from file/database
response = make_response({
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
],
"id": "https://example.com/users/zampano",
"inbox": "https://example.com/users/zampano/inbox",
"outbox": "https://example.com/users/zampano/outbox",
"type": "Person",
"name": "Zampano",
"preferredUsername": "zampano",
"publicKey": {
"id": "https://example.com/users/zampano#main-key",
"id": "https://example.com/users/zampano",
"publicKeyPem": public_key
}
})
# Servers may discard the result if you do not set the appropriate content type
response.headers['Content-Type'] = 'application/activity+json'
return response 4. Create a Webfinger endpoint for yourself"Web finger is used to discover information about people or other entities on the Internet that are identified by a URI." Some ActivityPub servers, like Mastodon, will use Webfinger to find the location of the Actor record we've been creating. from flask import request, make_response
# ...
@app.route('/.well-known/webfinger')
def webfinger():
resource = request.args.get('resource')
if resource != "acct:zampano@example.com":
abort(404)
response = make_response({
"subject": "acct:zampano@example.com",
"links": [
{
"rel": "self",
"type": "application/activity+json",
"href": "https://example.com/users/zampano"
}
]
})
# Servers may discard the result if you do not set the appropriate content type
response.headers['Content-Type'] = 'application/jrd+json'
return response 5. Create an inbox endpoint for yourselfWe've defined an inbox and outbox property in our Person record.
We will want to define the outbox later for the client-to-server interactions, but for now we can get away with just the inbox. @app.route('/users/<username>/inbox', methods=['POST'])
def user_inbox(username):
if username != "zampano":
abort(404)
app.logger.info(request.headers)
app.logger.info(request.data)
return Response("", status=202) 6. Follow a user from an instance to start receiving activities on your instanceYou could feasibly follow any ActivityPub Actor now but I recommend testing with an account you control on a Mastodon instance, or with a bot account. Let's assume you're sending a follow request to the user 'truant' at the Mastodon instance 'exampletwo.com'. from cryptography.hazmat.backends import default_backend as crypto_default_backend
from cryptography.hazmat.primitives import serialization as crypto_serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from urllib.parse import urlparse
import base64
import datetime
import requests
recipient_url = "https://exampletwo.com/users/truant"
recipient_inbox = "https://exampletwo.com/users/truant/inbox"
sender_url = "https://example.com/users/zampano"
sender_key = "https://example.com/users/zampano#main-key"
activity_id = "https://example.com/users/zampano/follows/test"
# The following is to sign the HTTP request as defined in HTTP Signatures.
private_key_text = b'' # load from file
private_key = crypto_serialization.load_pem_private_key(
private_key_text,
password=None,
backend=crypto_default_backend()
)
current_date = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
recipient_parsed = urlparse(recipient_inbox)
recipient_host = recipient_parsed.netloc
recipient_path = recipient_parsed.path
signature_text = b'(request-target): post %s\nhost: %s\ndate: %s' % recipient_path.encode('utf-8'), recipient_host.encode('utf-8'), date.encode('utf-8')
raw_signature = private_key.sign(
signature_text,
padding.PKCS1v15(),
hashes.SHA256()
)
signature_header = 'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date",signature="%s"' % sender_key, base64.b64encode(raw_signature).decode('utf-8')
headers = {
'Date': date,
'Content-Type': 'application/activity+json',
'Host': recipient_host,
'Signature': signature_header
}
# Now that the header is set up, we will construct the message
follow_request_message = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": activity_id,
"type": "Follow",
"actor": sender_url,
"object": recipient_url
}
r = requests.post(recipient_inbox, headers=headers, json=follow_request_message) Standards
Resources |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论