mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-11-22 06:27:59 +00:00
create Response class
This commit is contained in:
parent
6af9c8e6fe
commit
ff95a3033d
|
@ -10,6 +10,7 @@ from Crypto.Hash import SHA, SHA256, SHA512
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
from Crypto.Signature import PKCS1_v1_5
|
from Crypto.Signature import PKCS1_v1_5
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
|
from aiohttp.web import Response as AiohttpResponse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
@ -26,6 +27,13 @@ HASHES = {
|
||||||
'sha512': SHA512
|
'sha512': SHA512
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MIMETYPES = {
|
||||||
|
'activity': 'application/activity+json',
|
||||||
|
'html': 'text/html',
|
||||||
|
'json': 'application/json',
|
||||||
|
'plain': 'text/plain'
|
||||||
|
}
|
||||||
|
|
||||||
NODEINFO_NS = {
|
NODEINFO_NS = {
|
||||||
'20': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
'20': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
||||||
'21': 'http://nodeinfo.diaspora.software/ns/schema/2.1'
|
'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'
|
method = 'POST' if data else 'GET'
|
||||||
action = data.get('type') if data else None
|
action = data.get('type') if data else None
|
||||||
headers = {
|
headers = {
|
||||||
'Accept': 'application/activity+json, application/json;q=0.9',
|
'Accept': f'{MIMETYPES["activity"]}, {MIMETYPES["json"]};q=0.9',
|
||||||
'User-Agent': 'ActivityRelay',
|
'User-Agent': 'ActivityRelay',
|
||||||
}
|
}
|
||||||
|
|
||||||
if data:
|
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:
|
if sign_headers:
|
||||||
signing_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}')
|
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)
|
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)
|
resp_data = await resp.json(loads=DotDict.new_from_json)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -453,6 +461,45 @@ class Message(DotDict):
|
||||||
return self.object
|
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):
|
class WKNodeinfo(DotDict):
|
||||||
@classmethod
|
@classmethod
|
||||||
def new(cls, v20, v21):
|
def new(cls, v20, v21):
|
||||||
|
|
|
@ -2,16 +2,21 @@ import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from aiohttp.web import HTTPForbidden, HTTPUnauthorized, Response, json_response, route
|
|
||||||
|
|
||||||
from . import __version__, misc
|
from . import __version__, misc
|
||||||
from .http_debug import STATS
|
from .http_debug import STATS
|
||||||
from .misc import Message, WKNodeinfo
|
from .misc import Message, Response, WKNodeinfo
|
||||||
from .processors import run_processor
|
from .processors import run_processor
|
||||||
|
|
||||||
|
|
||||||
routes = []
|
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 register_route(method, path):
|
||||||
def wrapper(func):
|
def wrapper(func):
|
||||||
|
@ -21,14 +26,6 @@ def register_route(method, path):
|
||||||
return wrapper
|
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', '/')
|
@register_route('GET', '/')
|
||||||
async def home(request):
|
async def home(request):
|
||||||
targets = '<br>'.join(request.app.database.hostnames)
|
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>
|
<br><p>List of {count} registered instances:<br>{targets}</p>
|
||||||
</body></html>"""
|
</body></html>"""
|
||||||
|
|
||||||
return Response(
|
return Response.new(text, ctype='html')
|
||||||
status = 200,
|
|
||||||
content_type = 'text/html',
|
|
||||||
charset = 'utf-8',
|
|
||||||
text = text
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('GET', '/inbox')
|
@register_route('GET', '/inbox')
|
||||||
|
@ -71,7 +63,7 @@ async def actor(request):
|
||||||
pubkey = request.app.database.pubkey
|
pubkey = request.app.database.pubkey
|
||||||
)
|
)
|
||||||
|
|
||||||
return json_response(data, content_type='application/activity+json')
|
return Response.new(data, ctype='activity')
|
||||||
|
|
||||||
|
|
||||||
@register_route('POST', '/inbox')
|
@register_route('POST', '/inbox')
|
||||||
|
@ -95,29 +87,29 @@ async def inbox(request):
|
||||||
## reject if there is no actor in the message
|
## reject if there is no actor in the message
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logging.verbose('actor not in data')
|
logging.verbose('actor not in data')
|
||||||
raise HTTPUnauthorized(body='no actor in message')
|
return Response.new_error(400, 'no actor in message', 'json')
|
||||||
|
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
logging.verbose('Failed to parse inbox message')
|
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)
|
actor = await misc.request(data.actorid)
|
||||||
|
|
||||||
## reject if actor is empty
|
## reject if actor is empty
|
||||||
if not actor:
|
if not actor:
|
||||||
logging.verbose(f'Failed to fetch actor: {data.actorid}')
|
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
|
## reject if the actor isn't whitelisted while the whiltelist is enabled
|
||||||
elif config.whitelist_enabled and not config.is_whitelisted(data.domain):
|
elif config.whitelist_enabled and not config.is_whitelisted(data.domain):
|
||||||
logging.verbose(f'Rejected actor for not being in the whitelist: {data.actorid}')
|
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
|
## reject if actor is banned
|
||||||
if request.app['config'].is_banned(data.domain):
|
if request.app['config'].is_banned(data.domain):
|
||||||
logging.verbose(f'Ignored request from banned actor: {data.actorid}')
|
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
|
## reject if software used by actor is banned
|
||||||
if len(config.blocked_software):
|
if len(config.blocked_software):
|
||||||
|
@ -125,22 +117,22 @@ async def inbox(request):
|
||||||
|
|
||||||
if config.is_banned_software(software):
|
if config.is_banned_software(software):
|
||||||
logging.verbose(f'Rejected actor for using specific 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
|
## reject if the signature is invalid
|
||||||
if not (await misc.validate_signature(data.actorid, request)):
|
if not (await misc.validate_signature(data.actorid, request)):
|
||||||
logging.verbose(f'signature validation failed for: {data.actorid}')
|
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
|
## reject if activity type isn't 'Follow' and the actor isn't following
|
||||||
if data['type'] != 'Follow' and not database.get_inbox(data.domain):
|
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}')
|
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}")
|
logging.debug(f">> payload {data}")
|
||||||
|
|
||||||
await run_processor(request, actor, data, software)
|
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')
|
@register_route('GET', '/.well-known/webfinger')
|
||||||
|
@ -148,7 +140,7 @@ async def webfinger(request):
|
||||||
subject = request.query['resource']
|
subject = request.query['resource']
|
||||||
|
|
||||||
if subject != f'acct:relay@{request.app.config.host}':
|
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 = {
|
data = {
|
||||||
'subject': subject,
|
'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}')
|
@register_route('GET', '/nodeinfo/{version:\d.\d\.json}')
|
||||||
|
@ -191,7 +183,7 @@ async def nodeinfo_2_0(request):
|
||||||
if version == '2.1':
|
if version == '2.1':
|
||||||
data['software']['repository'] = 'https://git.pleroma.social/pleroma/relay'
|
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')
|
@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'
|
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')
|
@register_route('GET', '/stats')
|
||||||
async def stats(request):
|
async def stats(request):
|
||||||
return json_response(STATS)
|
return Response.new(STATS, ctype='json')
|
||||||
|
|
Loading…
Reference in a new issue