mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-11-22 06:27:59 +00:00
use new request properties and only fetch nodeinfo on follow
This commit is contained in:
parent
4a8a8da740
commit
ba9f2718aa
|
@ -6,92 +6,97 @@ from uuid import uuid4
|
||||||
from . import misc
|
from . import misc
|
||||||
|
|
||||||
|
|
||||||
async def handle_relay(request, actor, data, nodeinfo):
|
async def handle_relay(request):
|
||||||
if data.objectid in request.app.cache.objects:
|
if request.message.objectid in request.cache.objects:
|
||||||
logging.verbose(f'already relayed {data.objectid}')
|
logging.verbose(f'already relayed {request.message.objectid}')
|
||||||
return
|
return
|
||||||
|
|
||||||
logging.verbose(f'Relaying post from {data.actorid}')
|
logging.verbose(f'Relaying post from {request.message.actorid}')
|
||||||
|
|
||||||
message = misc.Message.new_announce(
|
message = misc.Message.new_announce(
|
||||||
host = request.app.config.host,
|
host = request.config.host,
|
||||||
object = data.objectid
|
object = request.message.objectid
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.debug(f'>> relay: {message}')
|
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]
|
futures = [misc.request(inbox, data=message) for inbox in inboxes]
|
||||||
|
|
||||||
asyncio.ensure_future(asyncio.gather(*futures))
|
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):
|
async def handle_forward(request):
|
||||||
if data.id in request.app.cache.objects:
|
if request.message.id in request.cache.objects:
|
||||||
logging.verbose(f'already forwarded {data.id}')
|
logging.verbose(f'already forwarded {request.message.id}')
|
||||||
return
|
return
|
||||||
|
|
||||||
message = misc.Message.new_announce(
|
message = misc.Message.new_announce(
|
||||||
host = request.app.config.host,
|
host = request.config.host,
|
||||||
object = data
|
object = request.message
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.verbose(f'Forwarding post from {actor.id}')
|
logging.verbose(f'Forwarding post from {request.actor.id}')
|
||||||
logging.debug(f'>> Relay {data}')
|
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]
|
futures = [misc.request(inbox, data=message) for inbox in inboxes]
|
||||||
|
|
||||||
asyncio.ensure_future(asyncio.gather(*futures))
|
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):
|
async def handle_follow(request):
|
||||||
if not request.app.database.add_inbox(actor.shared_inbox, data.id):
|
nodeinfo = await misc.fetch_nodeinfo(request.actor.domain)
|
||||||
request.app.database.set_followid(actor.id, data.id)
|
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(
|
await misc.request(
|
||||||
actor.shared_inbox,
|
request.actor.shared_inbox,
|
||||||
misc.Message.new_response(
|
misc.Message.new_response(
|
||||||
host = request.app.config.host,
|
host = request.config.host,
|
||||||
actor = actor.id,
|
actor = request.actor.id,
|
||||||
followid = data.id,
|
followid = request.message.id,
|
||||||
accept = True
|
accept = True
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Are Akkoma and Pleroma the only two that expect a follow back?
|
# Are Akkoma and Pleroma the only two that expect a follow back?
|
||||||
# Ignoring only Mastodon for now
|
# Ignoring only Mastodon for now
|
||||||
if nodeinfo.swname != 'mastodon':
|
if software != 'mastodon':
|
||||||
await misc.request(
|
await misc.request(
|
||||||
actor.shared_inbox,
|
request.actor.shared_inbox,
|
||||||
misc.Message.new_follow(
|
misc.Message.new_follow(
|
||||||
host = request.app.config.host,
|
host = request.config.host,
|
||||||
actor = actor.id
|
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 the object is not a Follow, forward it
|
||||||
if data['object']['type'] != 'Follow':
|
if request.message.object.type != 'Follow':
|
||||||
return await handle_forward(request, actor, data, nodeinfo)
|
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
|
return
|
||||||
|
|
||||||
request.app.database.save()
|
request.database.save()
|
||||||
|
|
||||||
message = misc.Message.new_unfollow(
|
message = misc.Message.new_unfollow(
|
||||||
host = request.app.config.host,
|
host = request.config.host,
|
||||||
actor = actor.id,
|
actor = request.actor.id,
|
||||||
follow = data
|
follow = request.message
|
||||||
)
|
)
|
||||||
|
|
||||||
await misc.request(actor.shared_inbox, message)
|
await misc.request(request.actor.shared_inbox, message)
|
||||||
|
|
||||||
|
|
||||||
processors = {
|
processors = {
|
||||||
|
@ -104,9 +109,9 @@ processors = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def run_processor(request, actor, data, nodeinfo):
|
async def run_processor(request):
|
||||||
if data.type not in processors:
|
if request.message.type not in processors:
|
||||||
return
|
return
|
||||||
|
|
||||||
logging.verbose(f'New "{data.type}" from actor: {actor.id}')
|
logging.verbose(f'New "{request.message.type}" from actor: {request.actor.id}')
|
||||||
return await processors[data.type](request, actor, data, nodeinfo)
|
return await processors[request.message.type](request)
|
||||||
|
|
|
@ -33,10 +33,10 @@ def register_route(method, path):
|
||||||
|
|
||||||
@register_route('GET', '/')
|
@register_route('GET', '/')
|
||||||
async def home(request):
|
async def home(request):
|
||||||
targets = '<br>'.join(request.app.database.hostnames)
|
targets = '<br>'.join(request.database.hostnames)
|
||||||
note = request.app.config.note
|
note = request.config.note
|
||||||
count = len(request.app.database.hostnames)
|
count = len(request.database.hostnames)
|
||||||
host = request.app.config.host
|
host = request.config.host
|
||||||
|
|
||||||
text = f"""
|
text = f"""
|
||||||
<html><head>
|
<html><head>
|
||||||
|
@ -64,8 +64,8 @@ a:hover {{ color: #8AF; }}
|
||||||
@register_route('GET', '/actor')
|
@register_route('GET', '/actor')
|
||||||
async def actor(request):
|
async def actor(request):
|
||||||
data = Message.new_actor(
|
data = Message.new_actor(
|
||||||
host = request.app.config.host,
|
host = request.config.host,
|
||||||
pubkey = request.app.database.pubkey
|
pubkey = request.database.pubkey
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response.new(data, ctype='activity')
|
return Response.new(data, ctype='activity')
|
||||||
|
@ -74,67 +74,63 @@ async def actor(request):
|
||||||
@register_route('POST', '/inbox')
|
@register_route('POST', '/inbox')
|
||||||
@register_route('POST', '/actor')
|
@register_route('POST', '/actor')
|
||||||
async def inbox(request):
|
async def inbox(request):
|
||||||
config = request.app.config
|
config = request.config
|
||||||
database = request.app.database
|
database = request.database
|
||||||
|
|
||||||
## reject if missing signature header
|
## reject if missing signature header
|
||||||
try:
|
if not request.signature:
|
||||||
signature = DotDict.new_from_signature(request.headers['signature'])
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
logging.verbose('Actor missing signature header')
|
logging.verbose('Actor missing signature header')
|
||||||
raise HTTPUnauthorized(body='missing signature')
|
raise HTTPUnauthorized(body='missing signature')
|
||||||
|
|
||||||
## read message
|
|
||||||
try:
|
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
|
## reject if there is no actor in the message
|
||||||
if 'actor' not in data:
|
if 'actor' not in request.message:
|
||||||
logging.verbose('actor not in data')
|
logging.verbose('actor not in message')
|
||||||
return Response.new_error(400, 'no actor in message', 'json')
|
return Response.new_error(400, 'no actor in message', 'json')
|
||||||
|
|
||||||
except:
|
except:
|
||||||
|
## this code should hopefully never get called
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
logging.verbose('Failed to parse inbox message')
|
logging.verbose('Failed to parse inbox message')
|
||||||
return Response.new_error(400, 'failed to parse message', 'json')
|
return Response.new_error(400, 'failed to parse message', 'json')
|
||||||
|
|
||||||
actor = await misc.request(signature.keyid)
|
request['actor'] = await misc.request(request.signature.keyid)
|
||||||
nodeinfo = await misc.fetch_nodeinfo(actor.domain)
|
|
||||||
|
|
||||||
## reject if actor is empty
|
## reject if actor is empty
|
||||||
if not actor:
|
if not request.actor:
|
||||||
logging.verbose(f'Failed to fetch actor: {actor.id}')
|
logging.verbose(f'Failed to fetch actor: {request.actor.id}')
|
||||||
return Response.new_error(400, 'failed to fetch actor', 'json')
|
return Response.new_error(400, 'failed to fetch actor', 'json')
|
||||||
|
|
||||||
## reject if the actor isn't whitelisted while the whiltelist is enabled
|
## reject if the actor isn't whitelisted while the whiltelist is enabled
|
||||||
elif config.whitelist_enabled and not config.is_whitelisted(actor.domain):
|
if config.whitelist_enabled and not config.is_whitelisted(request.actor.domain):
|
||||||
logging.verbose(f'Rejected actor for not being in the whitelist: {actor.id}')
|
logging.verbose(f'Rejected actor for not being in the whitelist: {request.actor.id}')
|
||||||
return Response.new_error(403, 'access denied', 'json')
|
return Response.new_error(403, 'access denied', 'json')
|
||||||
|
|
||||||
## reject if actor is banned
|
## 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}')
|
logging.verbose(f'Ignored request from banned actor: {actor.id}')
|
||||||
return Response.new_error(403, 'access denied', 'json')
|
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
|
## 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}')
|
logging.verbose(f'signature validation failed for: {actor.id}')
|
||||||
return Response.new_error(401, 'signature check failed', 'json')
|
return Response.new_error(401, 'signature check failed', 'json')
|
||||||
|
|
||||||
## reject if activity type isn't 'Follow' and the actor isn't following
|
## reject if activity type isn't 'Follow' and the actor isn't following
|
||||||
if data['type'] != 'Follow' and not database.get_inbox(actor.domain):
|
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: {actor.id}')
|
logging.verbose(f'Rejected actor for trying to post while not following: {request.actor.id}')
|
||||||
return Response.new_error(401, 'access denied', 'json')
|
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)
|
return Response.new(status=202)
|
||||||
|
|
||||||
|
|
||||||
|
@ -146,15 +142,15 @@ async def webfinger(request):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return Response.new_error(400, 'missing \'resource\' query key', 'json')
|
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')
|
return Response.new_error(404, 'user not found', 'json')
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'subject': subject,
|
'subject': subject,
|
||||||
'aliases': [request.app.config.actor],
|
'aliases': [request.config.actor],
|
||||||
'links': [
|
'links': [
|
||||||
{'href': request.app.config.actor, 'rel': 'self', 'type': 'application/activity+json'},
|
{'href': request.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/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"'}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +161,7 @@ async def webfinger(request):
|
||||||
async def nodeinfo_2_0(request):
|
async def nodeinfo_2_0(request):
|
||||||
niversion = request.match_info['version'][:3]
|
niversion = request.match_info['version'][:3]
|
||||||
data = {
|
data = {
|
||||||
'openRegistrations': not request.app.config.whitelist_enabled,
|
'openRegistrations': not request.config.whitelist_enabled,
|
||||||
'protocols': ['activitypub'],
|
'protocols': ['activitypub'],
|
||||||
'services': {
|
'services': {
|
||||||
'inbound': [],
|
'inbound': [],
|
||||||
|
@ -182,7 +178,7 @@ async def nodeinfo_2_0(request):
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'metadata': {
|
'metadata': {
|
||||||
'peers': request.app.database.hostnames
|
'peers': request.database.hostnames
|
||||||
},
|
},
|
||||||
'version': niversion
|
'version': niversion
|
||||||
}
|
}
|
||||||
|
@ -196,8 +192,8 @@ async def nodeinfo_2_0(request):
|
||||||
@register_route('GET', '/.well-known/nodeinfo')
|
@register_route('GET', '/.well-known/nodeinfo')
|
||||||
async def nodeinfo_wellknown(request):
|
async def nodeinfo_wellknown(request):
|
||||||
data = WKNodeinfo.new(
|
data = WKNodeinfo.new(
|
||||||
v20 = f'https://{request.app.config.host}/nodeinfo/2.0.json',
|
v20 = f'https://{request.config.host}/nodeinfo/2.0.json',
|
||||||
v21 = f'https://{request.app.config.host}/nodeinfo/2.1.json'
|
v21 = f'https://{request.config.host}/nodeinfo/2.1.json'
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response.new(data, ctype='json')
|
return Response.new(data, ctype='json')
|
||||||
|
|
Loading…
Reference in a new issue