fix webfinger, move functions, and handle other methods

This commit is contained in:
Izalia Mae 2020-01-19 23:01:08 -05:00
parent e04dd99dc1
commit c3efd9adc1
4 changed files with 54 additions and 89 deletions

View file

@ -1,11 +1,13 @@
import re
import json
import logging
import socket
import aiohttp
import validators
from http import client as http
from urllib.parse import urlparse
from colour import Color
from aiohttp_jinja2 import render_template as render
@ -94,6 +96,42 @@ def user_check(path):
return False
def parse_sig(signature, short=False):
if not signature:
return
for line in signature.split(','):
if 'keyid' in line.lower():
actor = line.split('=')[1].split('#')[0].replace('"', '')
return urlparse(actor).netloc if short else actor
def parse_ua(agent):
if not agent:
return
url = re.findall('https?://(?:[-\w.]|(?:%[\da-fA-F]))+', agent)
if not url:
if 'mozilla' not in agent.lower():
logging.debug('Failed to find url in user agent')
return 'unknown'
return urlparse(url[0]).netloc
def dig(domain):
if not domain or domain == 'unknown':
return
try:
return socket.gethostbyname(domain)
except socket.gaierror as e:
logging.info(f'Failed to resolve IP: {e}')
class color:
def __init__(self):
self.check = lambda color: Color(f'#{str(color)}' if re.search(r'^(?:[0-9a-fA-F]{3}){1,2}$', color) else color)

View file

@ -4,12 +4,10 @@ import logging
import binascii
import base64
import traceback
import socket
import aiohttp
from urllib.parse import urlparse, quote_plus
from http import client as http
from random import choice
from aiohttp_jinja2 import render_template as render
@ -18,7 +16,7 @@ from aiohttp.client_exceptions import *
from Crypto.PublicKey import RSA
from .signature import validate, pass_hash, sign_headers
from .functions import error, user_check, domain_check
from .functions import error, user_check, domain_check, parse_sig, parse_ua, dig
from .config import MASTOCONFIG, PAWSCONFIG, VERSION, script_path
from .database import pawsdb, query, trans, ban_check, user_ban_check, banned_user_check, wl_check, keys
@ -47,81 +45,19 @@ auth_paths = [
]
def parse_sig(signature, short=False):
if not signature:
return
for line in signature.split(','):
if 'keyid' in line.lower():
actor = line.split('=')[1].split('#')[0].replace('"', '')
return urlparse(actor).netloc if short else actor
def parse_ua(agent):
if not agent:
return
ua1 = agent.split('https://')
if len(ua1) < 2:
logging.debug(f'No url in user-agent: {agent}')
return 'unknown'
if 'mastodon' in agent:
ua2 = ua1[1].split('/')
elif 'pleroma' in agent:
ua2 = ua1[1].split(' <')
elif 'misskey' in agent or 'barksharkrelay' in agent:
ua2 = ua1[1].split(')')
elif 'friendica' in agent or 'microblog.pub' in agent or 'paws' in agent:
return ua1[1]
else:
logging.warning(f'Unhandled user-agent: {agent}')
return 'unknown'
if len(ua2) > 1:
logging.debug(f'domain: {ua2}')
return ua2[0]
logging.warning(f'Invalid user-agent: {ua2}')
def dig(domain):
if not domain or domain == 'unknown':
return
try:
return socket.gethostbyname(domain)
except socket.gaierror as e:
logging.info(f'Failed to resolve IP: {e}')
async def raise_auth_error(request, auth_realm):
raise aiohttp.web.HTTPUnauthorized(
headers={aiohttp.hdrs.WWW_AUTHENTICATE: f'Basic realm={auth_realm}'},
body=open(f'{script_path}/templates/unauthorized.html').read(),
content_type='text/html'
)
async def passthrough(path, headers, urlquery=None, is_json=False):
async def passthrough(request, headers, urlquery=None, is_json=False):
QUERY = urlquery if urlquery else ''
try:
# I don't think I need this, but I'll leave it here just in case
#async with aiohttp.request(reqtype, f'https://{MASTOCONFIG["domain"]}/{path}{query}', headers=headers, data=post) as resp:
async with aiohttp.request('GET', f'http://localhost:3000{path}?{urlquery}', headers=headers) as resp:
async with aiohttp.request(request.method, f'http://localhost:3000{request.path}?{urlquery}', headers=headers) as resp:
data = await resp.read()
if resp.status != 200:
logging.debug(data)
logging.info(f'Recieved error {resp.status} from Mastodon')
logging.warning(data)
logging.warning(f'Recieved error {resp.status} from Mastodon')
return error(resp.status, f'Failed to forward request. Recieved error {resp.status} from Mastodon')
return aiohttp.web.HTTPOk(body=data, content_type=resp.content_type)
@ -214,7 +150,7 @@ async def http_filter(app, handler):
urlquery = rawquery if rawquery != '?' else None
# This is supposed to sign whitelisted unsigned GETs, but it didn't work
if not signature and real_ip == ua_ip and wl_check(domain):
if not signature and real_ip == ua_ip and wl_check(domain) and request.method == 'GET':
logging.info(f'Trying to sign headers for {domain}')
host = MASTOCONFIG['domain']
@ -225,10 +161,10 @@ async def http_filter(app, handler):
'Accept': 'application/json',
'User-Agent': f'PAWS/{VERSION}; https://{host}',
'Host': host,
'X-Forwarded-For': request.headers.get('X-Forwarded-For', 'localhost'),
'X-Forwarded-For': request.headers.get('X-Forwarded-For', request.remote),
'X-Forwarded-Port': '443',
'X-Forwarded-Proto': 'https',
'X-Real-Ip': request.headers.get('X-Real-Ip', 'localhost')
'X-Real-Ip': request.headers.get('X-Real-Ip', request.remote)
}
HEADERS['signature'] = sign_headers(HEADERS, 'default', f'https://{paws_host}/paws/actor#main-key')
HEADERS.pop('(request-target)')
@ -238,7 +174,7 @@ async def http_filter(app, handler):
is_json = True if request.get('reqtype') == 'json' else False
get_data = await passthrough(request.path, HEADERS, urlquery=urlquery, is_json=is_json)
get_data = await passthrough(request, HEADERS, urlquery=urlquery, is_json=is_json)
return get_data
return (await handler(request))

View file

@ -23,6 +23,7 @@ REDIR_URI = f'https://{MASTOCONFIG["domain"]}/paws/auth'
WEBSITE = 'https://git.barkshark.xyz/izaliamae/paws'
# some instances use a different domain for federation
def get_webhost(domain):
try:
conn = http.HTTPSConnection(domain)

View file

@ -34,7 +34,6 @@ async def post_login(request):
return aiohttp.web.HTTPFound('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
domain = domain_check(domain)
print(domain)
if not domain:
return http_error(request, 200, 'Invalid domain')
@ -117,6 +116,7 @@ async def get_style(request):
async def get_paws(request):
'heck'
return aiohttp.web.json_response('UvU', status=200)
@ -124,41 +124,31 @@ async def post_paws(request):
return aiohttp.web.json_response('UvU', status=200)
async def get_json(request):
user = request.match_info['user'].replace('.json', '')
toot = request.match_info['toot'].replace('.json', '')
domain = MASTOCONFIG['domain']
return aiohttp.web.json_response(data)
async def get_webfinger(request):
domain = PAWSCONFIG['domain']
res = request.rel_url.query.get('resource')
if not res or res != 'paws':
if not res or res != f'acct:paws@{domain}':
data = {}
else:
data = {
"aliases": [
"https://{domain}/paws/actor"
f"https://{domain}/paws/actor"
],
"links": [
{
"href": "https://{domain}/paws/actor",
"href": f"https://{domain}/paws/actor",
"rel": "self",
"type": "application/activity+json"
},
{
"href": "https://{domain}/paws/actor",
"href": f"https://{domain}/paws/actor",
"rel": "self",
"type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
}
],
"subject": "acct:paws@{domain}"
"subject": f"acct:paws@{domain}"
}
return aiohttp.web.json_response(data)