create Response class

This commit is contained in:
Izalia Mae 2022-11-09 05:58:35 -05:00
parent 6af9c8e6fe
commit ff95a3033d
2 changed files with 75 additions and 36 deletions

View file

@ -10,6 +10,7 @@ from Crypto.Hash import SHA, SHA256, SHA512
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from aiohttp import ClientSession
from aiohttp.web import Response as AiohttpResponse
from datetime import datetime
from json.decoder import JSONDecodeError
from urllib.parse import urlparse
@ -26,6 +27,13 @@ HASHES = {
'sha512': SHA512
}
MIMETYPES = {
'activity': 'application/activity+json',
'html': 'text/html',
'json': 'application/json',
'plain': 'text/plain'
}
NODEINFO_NS = {
'20': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
'21': 'http://nodeinfo.diaspora.software/ns/schema/2.1'
@ -166,12 +174,12 @@ async def request(uri, data=None, force=False, sign_headers=True, activity=True)
method = 'POST' if data else 'GET'
action = data.get('type') if data else None
headers = {
'Accept': 'application/activity+json, application/json;q=0.9',
'Accept': f'{MIMETYPES["activity"]}, {MIMETYPES["json"]};q=0.9',
'User-Agent': 'ActivityRelay',
}
if data:
headers['Content-Type'] = 'application/activity+json' if activity else 'application/json'
headers['Content-Type'] = MIMETYPES['activity' if activity else 'json']
if sign_headers:
signing_headers = {
@ -219,10 +227,10 @@ async def request(uri, data=None, force=False, sign_headers=True, activity=True)
return logging.verbose(f'Received error when sending {action} to {uri}: {resp.status} {resp_data}')
if resp.content_type == 'application/activity+json':
if resp.content_type == MIMETYPES['activity']:
resp_data = await resp.json(loads=Message.new_from_json)
elif resp.content_type == 'application/json':
elif resp.content_type == MIMETYPES['json']:
resp_data = await resp.json(loads=DotDict.new_from_json)
else:
@ -453,6 +461,45 @@ class Message(DotDict):
return self.object
class Response(AiohttpResponse):
@classmethod
def new(cls, body='', status=200, headers=None, ctype='text'):
kwargs = {
'status': status,
'headers': headers,
'content_type': MIMETYPES[ctype]
}
if isinstance(body, bytes):
kwargs['body'] = body
elif isinstance(body, dict) and ctype in {'json', 'activity'}:
kwargs['text'] = json.dumps(body)
else:
kwargs['text'] = body
return cls(**kwargs)
@classmethod
def new_error(cls, status, body, ctype='plain'):
if ctype == 'json':
body = json.dumps({'status': status, 'error': body})
return cls(body=body, status=status, ctype=ctype)
@property
def location(self):
return self.headers.get('Location')
@location.setter
def location(self, value):
self.headers['Location'] = value
class WKNodeinfo(DotDict):
@classmethod
def new(cls, v20, v21):

View file

@ -2,16 +2,21 @@ import logging
import subprocess
import traceback
from aiohttp.web import HTTPForbidden, HTTPUnauthorized, Response, json_response, route
from . import __version__, misc
from .http_debug import STATS
from .misc import Message, WKNodeinfo
from .misc import Message, Response, WKNodeinfo
from .processors import run_processor
routes = []
try:
commit_label = subprocess.check_output(["git", "rev-parse", "HEAD"]).strip().decode('ascii')
version = f'{__version__} {commit_label}'
except:
version = __version__
def register_route(method, path):
def wrapper(func):
@ -21,14 +26,6 @@ def register_route(method, path):
return wrapper
try:
commit_label = subprocess.check_output(["git", "rev-parse", "HEAD"]).strip().decode('ascii')
version = f'{__version__} {commit_label}'
except:
version = __version__
@register_route('GET', '/')
async def home(request):
targets = '<br>'.join(request.app.database.hostnames)
@ -55,12 +52,7 @@ a:hover {{ color: #8AF; }}
<br><p>List of {count} registered instances:<br>{targets}</p>
</body></html>"""
return Response(
status = 200,
content_type = 'text/html',
charset = 'utf-8',
text = text
)
return Response.new(text, ctype='html')
@register_route('GET', '/inbox')
@ -71,7 +63,7 @@ async def actor(request):
pubkey = request.app.database.pubkey
)
return json_response(data, content_type='application/activity+json')
return Response.new(data, ctype='activity')
@register_route('POST', '/inbox')
@ -95,29 +87,29 @@ async def inbox(request):
## reject if there is no actor in the message
except KeyError:
logging.verbose('actor not in data')
raise HTTPUnauthorized(body='no actor in message')
return Response.new_error(400, 'no actor in message', 'json')
except:
traceback.print_exc()
logging.verbose('Failed to parse inbox message')
raise HTTPUnauthorized(body='failed to parse message')
return Response.new_error(400, 'failed to parse message', 'json')
actor = await misc.request(data.actorid)
## reject if actor is empty
if not actor:
logging.verbose(f'Failed to fetch actor: {data.actorid}')
raise HTTPUnauthorized('failed to fetch actor')
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(data.domain):
logging.verbose(f'Rejected actor for not being in the whitelist: {data.actorid}')
raise HTTPForbidden(body='access denied')
return Response.new_error(403, 'access denied', 'json')
## reject if actor is banned
if request.app['config'].is_banned(data.domain):
logging.verbose(f'Ignored request from banned actor: {data.actorid}')
raise HTTPForbidden(body='access denied')
return Response.new_error(403, 'access denied', 'json')
## reject if software used by actor is banned
if len(config.blocked_software):
@ -125,22 +117,22 @@ async def inbox(request):
if config.is_banned_software(software):
logging.verbose(f'Rejected actor for using specific software: {software}')
raise HTTPForbidden(body='access denied')
return Response.new_error(403, 'access denied', 'json')
## reject if the signature is invalid
if not (await misc.validate_signature(data.actorid, request)):
logging.verbose(f'signature validation failed for: {data.actorid}')
raise HTTPUnauthorized(body='signature check failed, signature did not match key')
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(data.domain):
logging.verbose(f'Rejected actor for trying to post while not following: {data.actorid}')
raise HTTPUnauthorized(body='access denied')
return Response.new_error(401, 'access denied', 'json')
logging.debug(f">> payload {data}")
await run_processor(request, actor, data, software)
return Response(body=b'{}', content_type='application/activity+json')
return Response.new(status=202)
@register_route('GET', '/.well-known/webfinger')
@ -148,7 +140,7 @@ async def webfinger(request):
subject = request.query['resource']
if subject != f'acct:relay@{request.app.config.host}':
return json_response({'error': 'user not found'}, status=404)
return Response.new_error(404, 'user not found', 'json')
data = {
'subject': subject,
@ -159,7 +151,7 @@ async def webfinger(request):
]
}
return json_response(data)
return Response.new(data, ctype='json')
@register_route('GET', '/nodeinfo/{version:\d.\d\.json}')
@ -191,7 +183,7 @@ async def nodeinfo_2_0(request):
if version == '2.1':
data['software']['repository'] = 'https://git.pleroma.social/pleroma/relay'
return json_response(data)
return Response.new(data, ctype='json')
@register_route('GET', '/.well-known/nodeinfo')
@ -201,9 +193,9 @@ async def nodeinfo_wellknown(request):
v21 = f'https://{request.app.config.host}/nodeinfo/2.1.json'
)
return json_response(data)
return Response.new(data, ctype='json')
@register_route('GET', '/stats')
async def stats(request):
return json_response(STATS)
return Response.new(STATS, ctype='json')