python flask implementation of activity pub actor
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

122 lines
3.9 KiB

# This is a simple python flask implementation of 'How To Implement A Basic ActivityPub Server'
# https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/
# © 2018 homebrewserver.club contributors
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import flask
import os
import requests
import base64
import json
from flask import request
from flask import Response
from time import strftime, gmtime
from Crypto.Hash import SHA256
from Crypto.Signature import pkcs1_15
from Crypto.PublicKey import RSA
from httpsig import HTTPSigAuth
from key import Key, get_key
#
import logging
import http.client as http_client
http_client.HTTPConnection.debuglevel = 1
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
#
#Config
DOMAIN = 'https://my-example.com'
USERNAME = 'alice'
key = get_key(f'{DOMAIN}/users/{USERNAME}') #generate keypair on first launch
#Flask
app = flask.Flask(__name__)
@app.route('/')
def index():
return 'It works! Now try to look for a user@my-example.com via the mastodon interface'
@app.route('/.well-known/webfinger')
def finger():
"""
Respond to webfinger queries (GET /.well-known/webfinger?resource=acct:alice@my-example.com) with a json object pointing to the actor
see templates/webfinger.json
"""
if request.args.get('resource'):
query = request.args.get('resource')
actor = query.split(':')[1].split('@')[0] # from 'acct:alice@my-example.com' to 'alice'
json = flask.render_template('webfinger.json', query=query, actor=actor, domain=DOMAIN) # render our ActivityPub answer
return Response(response=json, status=200, mimetype="application/json") # return that answer as a json object
@app.route('/users/<actor>')
def profile(actor):
"""
Return an Actor object
see templates/actor.json
"""
key = get_key(f'{DOMAIN}/users/{actor}')
publicKeyPem = key.to_dict()['publicKeyPem']
publicKeyPem = publicKeyPem.replace('\n','\\n') #public key shouldn't contain verbatim linebreaks in json
json = flask.render_template('actor.json', preferred_username=USERNAME, actor=actor, domain=DOMAIN, public_key=publicKeyPem) # render our ActivityPub answer
return Response(response=json, status=200, mimetype="application/json") # return that answer as a json object
@app.route('/post/', methods=['POST','GET'])
def post():
#key = get_key(f'{DOMAIN}/users/{USERNAME}')
headers = {"content-type": "application/activity+json",
"user-agent": f"Basic AP v: 0.0"}
activity=json.loads(flask.render_template('create.json',
domain=DOMAIN, actor=USERNAME))
http_sig = HTTPSigAuth(key, headers)
url = 'https://post.lurk.org/inbox'
body = json.dumps(activity)
headers = http_sig.sign(url,body)
r = requests.post(url, json=body, headers=headers)
html = f'Status code <pre>{r.status_code}</pre></br>Response Headers: <pre>{r.headers}</pre></br></br> json message: <pre>{r.request.body}</pre></br></br> Request headers: <pre>{r.request.headers}</pre>'
html_headers = f'<html><head><style>pre {{overflow:auto;background-color:#eee;}}</style></head><body>{html}</body></html>'
return html_headers
if __name__ == '__main__':
app.debug =True
app.run()