• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

timmot/activity-pub-tutorial: Get up and running with ActivityPub quickly.

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

timmot/activity-pub-tutorial

开源软件地址(OpenSource Url):

https://github.com/timmot/activity-pub-tutorial

开源编程语言(OpenSource Language):


开源软件介绍(OpenSource Introduction):

ActivityPub

ActivityPub Tutorial

Requirements

  • You need a domain name
  • You need an HTTPS certificate for this domain name
  • Python3
  • Python3 packages: flask, cryptography, requests

Short method

  1. Read the ActivityPub overview
  2. Create an endpoint for yourself
  3. Extend this endpoint for real servers
  4. Create a Webfinger endpoint for yourself
  5. Create an inbox endpoint for yourself
  6. Follow a user from an instance to start receiving activities on your instance

Long method

We'll run with some assumptions. Your domain name is example.com, your user name will be zampano.

1. Read the ActivityPub overview

https://www.w3.org/TR/activitypub/#Overview

2. Create an endpoint for yourself

https://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 servers

This 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 keys

openssl 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 yourself

We've defined an inbox and outbox property in our Person record.

...
"inbox": "https://example.com/users/zampano/inbox",
"outbox": "https://example.com/users/zampano/outbox",
...

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 instance

You 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




鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap