use new request properties and only fetch nodeinfo on follow

This commit is contained in:
Izalia Mae 2022-11-18 16:41:14 -05:00
parent 4a8a8da740
commit ba9f2718aa
2 changed files with 84 additions and 83 deletions

View file

@ -6,92 +6,97 @@ from uuid import uuid4
from . import misc
async def handle_relay(request, actor, data, nodeinfo):
if data.objectid in request.app.cache.objects:
logging.verbose(f'already relayed {data.objectid}')
async def handle_relay(request):
if request.message.objectid in request.cache.objects:
logging.verbose(f'already relayed {request.message.objectid}')
return
logging.verbose(f'Relaying post from {data.actorid}')
logging.verbose(f'Relaying post from {request.message.actorid}')
message = misc.Message.new_announce(
host = request.app.config.host,
object = data.objectid
host = request.config.host,
object = request.message.objectid
)
logging.debug(f'>> relay: {message}')
inboxes = misc.distill_inboxes(actor, data.objectid)
inboxes = misc.distill_inboxes(request.actor, request.message.objectid)
futures = [misc.request(inbox, data=message) for inbox in inboxes]
asyncio.ensure_future(asyncio.gather(*futures))
request.app.cache.objects[data.objectid] = message.id
request.cache.objects[request.message.objectid] = message.id
async def handle_forward(request, actor, data, nodeinfo):
if data.id in request.app.cache.objects:
logging.verbose(f'already forwarded {data.id}')
async def handle_forward(request):
if request.message.id in request.cache.objects:
logging.verbose(f'already forwarded {request.message.id}')
return
message = misc.Message.new_announce(
host = request.app.config.host,
object = data
host = request.config.host,
object = request.message
)
logging.verbose(f'Forwarding post from {actor.id}')
logging.debug(f'>> Relay {data}')
logging.verbose(f'Forwarding post from {request.actor.id}')
logging.debug(f'>> Relay {request.message}')
inboxes = misc.distill_inboxes(actor, data.id)
inboxes = misc.distill_inboxes(request.actor, request.message.id)
futures = [misc.request(inbox, data=message) for inbox in inboxes]
asyncio.ensure_future(asyncio.gather(*futures))
request.app.cache.objects[data.id] = message.id
request.cache.objects[request.message.id] = message.id
async def handle_follow(request, actor, data, nodeinfo):
if not request.app.database.add_inbox(actor.shared_inbox, data.id):
request.app.database.set_followid(actor.id, data.id)
async def handle_follow(request):
nodeinfo = await misc.fetch_nodeinfo(request.actor.domain)
software = nodeinfo.swname if nodeinfo else None
request.app.database.save()
## reject if software used by actor is banned
if request.config.is_banned_software(software):
return logging.verbose(f'Rejected follow from actor for using specific software: actor={request.actor.id}, software={software}')
request.database.add_inbox(request.actor.shared_inbox, request.message.id, software)
request.database.save()
await misc.request(
actor.shared_inbox,
request.actor.shared_inbox,
misc.Message.new_response(
host = request.app.config.host,
actor = actor.id,
followid = data.id,
host = request.config.host,
actor = request.actor.id,
followid = request.message.id,
accept = True
)
)
# Are Akkoma and Pleroma the only two that expect a follow back?
# Ignoring only Mastodon for now
if nodeinfo.swname != 'mastodon':
if software != 'mastodon':
await misc.request(
actor.shared_inbox,
request.actor.shared_inbox,
misc.Message.new_follow(
host = request.app.config.host,
actor = actor.id
host = request.config.host,
actor = request.actor.id
)
)
async def handle_undo(request, actor, data, nodeinfo):
async def handle_undo(request):
## If the object is not a Follow, forward it
if data['object']['type'] != 'Follow':
return await handle_forward(request, actor, data, nodeinfo)
if request.message.object.type != 'Follow':
return await handle_forward(request)
if not request.app.database.del_inbox(actor.domain, data.id):
if not request.database.del_inbox(request.actor.domain, request.message.id):
return
request.app.database.save()
request.database.save()
message = misc.Message.new_unfollow(
host = request.app.config.host,
actor = actor.id,
follow = data
host = request.config.host,
actor = request.actor.id,
follow = request.message
)
await misc.request(actor.shared_inbox, message)
await misc.request(request.actor.shared_inbox, message)
processors = {
@ -104,9 +109,9 @@ processors = {
}
async def run_processor(request, actor, data, nodeinfo):
if data.type not in processors:
async def run_processor(request):
if request.message.type not in processors:
return
logging.verbose(f'New "{data.type}" from actor: {actor.id}')
return await processors[data.type](request, actor, data, nodeinfo)
logging.verbose(f'New "{request.message.type}" from actor: {request.actor.id}')
return await processors[request.message.type](request)

View file

@ -33,10 +33,10 @@ def register_route(method, path):
@register_route('GET', '/')
async def home(request):
targets = '<br>'.join(request.app.database.hostnames)
note = request.app.config.note
count = len(request.app.database.hostnames)
host = request.app.config.host
targets = '<br>'.join(request.database.hostnames)
note = request.config.note
count = len(request.database.hostnames)
host = request.config.host
text = f"""
<html><head>
@ -64,8 +64,8 @@ a:hover {{ color: #8AF; }}
@register_route('GET', '/actor')
async def actor(request):
data = Message.new_actor(
host = request.app.config.host,
pubkey = request.app.database.pubkey
host = request.config.host,
pubkey = request.database.pubkey
)
return Response.new(data, ctype='activity')
@ -74,67 +74,63 @@ async def actor(request):
@register_route('POST', '/inbox')
@register_route('POST', '/actor')
async def inbox(request):
config = request.app.config
database = request.app.database
config = request.config
database = request.database
## reject if missing signature header
try:
signature = DotDict.new_from_signature(request.headers['signature'])
except KeyError:
if not request.signature:
logging.verbose('Actor missing signature header')
raise HTTPUnauthorized(body='missing signature')
## read message
try:
data = await request.json(loads=Message.new_from_json)
request['message'] = await request.json(loads=Message.new_from_json)
## reject if there is no message
if not request.message:
logging.verbose('empty message')
return Response.new_error(400, 'missing message', 'json')
## reject if there is no actor in the message
if 'actor' not in data:
logging.verbose('actor not in data')
if 'actor' not in request.message:
logging.verbose('actor not in message')
return Response.new_error(400, 'no actor in message', 'json')
except:
## this code should hopefully never get called
traceback.print_exc()
logging.verbose('Failed to parse inbox message')
return Response.new_error(400, 'failed to parse message', 'json')
actor = await misc.request(signature.keyid)
nodeinfo = await misc.fetch_nodeinfo(actor.domain)
request['actor'] = await misc.request(request.signature.keyid)
## reject if actor is empty
if not actor:
logging.verbose(f'Failed to fetch actor: {actor.id}')
if not request.actor:
logging.verbose(f'Failed to fetch actor: {request.actor.id}')
return Response.new_error(400, 'failed to fetch actor', 'json')
## reject if the actor isn't whitelisted while the whiltelist is enabled
elif config.whitelist_enabled and not config.is_whitelisted(actor.domain):
logging.verbose(f'Rejected actor for not being in the whitelist: {actor.id}')
if config.whitelist_enabled and not config.is_whitelisted(request.actor.domain):
logging.verbose(f'Rejected actor for not being in the whitelist: {request.actor.id}')
return Response.new_error(403, 'access denied', 'json')
## reject if actor is banned
if request.app['config'].is_banned(actor.domain):
if request.config.is_banned(request.actor.domain):
logging.verbose(f'Ignored request from banned actor: {actor.id}')
return Response.new_error(403, 'access denied', 'json')
## reject if software used by actor is banned
if config.is_banned_software(nodeinfo.swname):
logging.verbose(f'Rejected actor for using specific software: {nodeinfo.swname}')
return Response.new_error(403, 'access denied', 'json')
## reject if the signature is invalid
if not (await misc.validate_signature(actor, signature, request)):
if not (await misc.validate_signature(request.actor, request.signature, request)):
logging.verbose(f'signature validation failed for: {actor.id}')
return Response.new_error(401, 'signature check failed', 'json')
## reject if activity type isn't 'Follow' and the actor isn't following
if data['type'] != 'Follow' and not database.get_inbox(actor.domain):
logging.verbose(f'Rejected actor for trying to post while not following: {actor.id}')
if request.message.type != 'Follow' and not database.get_inbox(request.actor.domain):
logging.verbose(f'Rejected actor for trying to post while not following: {request.actor.id}')
return Response.new_error(401, 'access denied', 'json')
logging.debug(f">> payload {data}")
logging.debug(f">> payload {request.message.to_json(4)}")
await run_processor(request, actor, data, nodeinfo)
await run_processor(request)
return Response.new(status=202)
@ -146,15 +142,15 @@ async def webfinger(request):
except KeyError:
return Response.new_error(400, 'missing \'resource\' query key', 'json')
if subject != f'acct:relay@{request.app.config.host}':
if subject != f'acct:relay@{request.config.host}':
return Response.new_error(404, 'user not found', 'json')
data = {
'subject': subject,
'aliases': [request.app.config.actor],
'aliases': [request.config.actor],
'links': [
{'href': request.app.config.actor, 'rel': 'self', 'type': 'application/activity+json'},
{'href': request.app.config.actor, 'rel': 'self', 'type': 'application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"'}
{'href': request.config.actor, 'rel': 'self', 'type': 'application/activity+json'},
{'href': request.config.actor, 'rel': 'self', 'type': 'application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"'}
]
}
@ -165,7 +161,7 @@ async def webfinger(request):
async def nodeinfo_2_0(request):
niversion = request.match_info['version'][:3]
data = {
'openRegistrations': not request.app.config.whitelist_enabled,
'openRegistrations': not request.config.whitelist_enabled,
'protocols': ['activitypub'],
'services': {
'inbound': [],
@ -182,7 +178,7 @@ async def nodeinfo_2_0(request):
}
},
'metadata': {
'peers': request.app.database.hostnames
'peers': request.database.hostnames
},
'version': niversion
}
@ -196,8 +192,8 @@ async def nodeinfo_2_0(request):
@register_route('GET', '/.well-known/nodeinfo')
async def nodeinfo_wellknown(request):
data = WKNodeinfo.new(
v20 = f'https://{request.app.config.host}/nodeinfo/2.0.json',
v21 = f'https://{request.app.config.host}/nodeinfo/2.1.json'
v20 = f'https://{request.config.host}/nodeinfo/2.0.json',
v21 = f'https://{request.config.host}/nodeinfo/2.1.json'
)
return Response.new(data, ctype='json')