honor user domain blocks and many changes

This commit is contained in:
Izalia Mae 2020-01-20 23:54:30 -05:00
parent 545a68eeb1
commit 22fe37d38a
10 changed files with 154 additions and 53 deletions

View file

@ -72,3 +72,6 @@ While it isn't necessary, I highly recommend turning on authorized fetches (v3.0
AUTHORIZED_FETCH=true
```
## WebUI usage
If your account is an admin account according to Mastodon, you can manage the PAWS whitelist at {domain}/paws. Instances in this whitelist will have their fetches signed if they don't sign them themselves.

View file

@ -167,8 +167,21 @@ def user_ban_check(user, access_user):
return False
def user_domain_ban_check(handle, access_domain):
user_data = get_user(handle)
if not user_data:
return
userid = user_data['id']
ban_data = mastodb.query(f'SELECT * FROM public.account_domain_blocks WHERE account_id = \'{userid}\' and domain = \'{access_domain}\'').dictresult()
return ban_data
def wl_check(domain):
return pawsdb.whitelist.get(query.domain == domain)
data = pawsdb.whitelist.get(query.domain == domain)
return data
def admin_check(handle):
@ -206,4 +219,3 @@ def whitelist(action, instance):
pawsdb = jsondb()
query = Query()
mastodb = pgdb()

View file

@ -2,6 +2,7 @@ import re
import json
import logging
import socket
import os
import aiohttp
import validators
@ -132,6 +133,17 @@ def dig(domain):
logging.info(f'Failed to resolve IP: {e}')
def css_ts():
from .config import script_path
css_check = lambda css_file : int(os.path.getmtime(f'{script_path}/templates/{css_file}.css'))
color = css_check('color')
layout = css_check('layout')
return color + layout
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

@ -18,7 +18,11 @@ from Crypto.PublicKey import RSA
from .signature import validate, pass_hash, sign_headers
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
from .database import pawsdb, query, trans, ban_check, user_ban_check, banned_user_check, wl_check, keys, user_domain_ban_check
paws_host = PAWSCONFIG['domain']
masto_host = MASTOCONFIG['domain']
# I'm a little teapot :3
@ -103,7 +107,7 @@ async def http_filter(app, handler):
logging.info(f'Blocked user: {domain}')
return http_error(request, 403, 'Access Denied')
if any(map(request.path.startswith, auth_paths)):
if any(map(request.path.startswith, auth_paths)) and request.method == 'GET':
if 'json' in request.headers.get('Accept', '') or request.path.endswith('.json'):
request['reqtype'] = 'json'
@ -125,16 +129,15 @@ async def http_filter(app, handler):
raise error(401, msg)
else:
logging.error('heck')
request['reqtype'] = 'html'
if not token or not user_data:
return aiohttp.web.HTTPFound(f'/paws/login?redir={quote_plus(request.path)}')
return aiohttp.web.HTTPFound(f'https://{masto_host}/paws/login?redir={quote_plus(request.path)}')
split_path = request.path.split('/')
user = split_path[1].replace('@', '') if request.path.startswith('/@') else split_path[2]
if user_ban_check(user.lower(), (user_data['handle'].lower(), user_data['instance'])):
if user_ban_check(user.lower(), (user_data['handle'].lower(), user_data['instance'])) or user_domain_ban_check(user.lower(), user_data['instance']):
return http_error(request, 403, 'Access Denied')
querydata = request.query
@ -150,18 +153,14 @@ 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) and request.method == 'GET':
logging.info(f'Signing fetch for whitelisted instance: {domain}')
host = MASTOCONFIG['domain']
paws_host = PAWSCONFIG['domain']
HEADERS = {
'(request-target)': f'get {request.path}',
'Accept': 'application/json',
'User-Agent': f'PAWS/{VERSION}; https://{host}',
'Host': host,
'User-Agent': f'PAWS/{VERSION}; https://{masto_host}',
'Host': masto_host,
'X-Forwarded-For': request.headers.get('X-Forwarded-For', request.remote),
'X-Forwarded-Port': '443',
'X-Forwarded-Proto': 'https',

View file

@ -9,7 +9,7 @@ from ipaddress import ip_address as address
from urllib.parse import urlparse
from .config import PAWSCONFIG, MASTOCONFIG, VERSION, script_path, logging
from .functions import color
from .functions import color, css_ts
from . import middleware
@ -26,22 +26,27 @@ def webserver():
web.add_routes([
aiohttp.web.route('GET', '/', views.get_home),
aiohttp.web.route('GET', '/paws', views.get_paws),
aiohttp.web.route('GET', '/paws/imgay', views.get_gay),
aiohttp.web.route('POST', '/paws/action/{action}', views.post_paws),
aiohttp.web.route('GET', '/paws/login', views.get_login),
aiohttp.web.route('POST', '/paws/login', views.post_login),
aiohttp.web.route('GET', '/paws/logout', views.get_logout),
aiohttp.web.route('GET', '/paws/auth', views.get_auth),
aiohttp.web.route('GET', '/paws/style.css', views.get_style),
aiohttp.web.route('GET', '/paws/style-{timestamp}.css', views.get_style),
aiohttp.web.route('GET', '/.well-known/webfinger', views.get_webfinger),
aiohttp.web.route('POST', '/paws/inbox', views.post_inbox),
aiohttp.web.route('GET', '/paws/actor', views.get_actor)
aiohttp.web.route('GET', '/paws/actor', views.get_actor),
#aiohttp.web.route('GET', '/paws/outbox', views.get_outbox),
#aiohttp.web.route('GET', '/paws/imgay', views.get_gay)
])
async def global_vars(request):
return {
'VERSION': VERSION,
'len': len,
'css_ts': css_ts,
'request': request,
'domain': MASTOCONFIG['domain'],
'urlparse': urlparse,

View file

@ -7,7 +7,8 @@
<html>
<head>
<title>PAWS@{{domain}}: {{page}}</title>
<link rel=stylesheet href="/paws/style.css" media="screen">
<link rel=stylesheet href="/paws/style-{{css_ts()}}.css" media="screen">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body>
<div id="content">
@ -22,7 +23,7 @@
</div>
<div class="grid-item col2">
{% if request.user %}
<p>{{request.user.handle}} [<a href="/paws/logout">logout</a>]</p>
<p>{{request.user.handle}}@{{request.user.instance}} [<a href="/paws/logout">logout</a>]</p>
{% else %}
<p>Guest [<a href="/paws/login">login</a>]</p>
{% endif %}

View file

@ -36,7 +36,6 @@ summary:hover {
#header h1 {
margin: 0px;
padding-top: 20px;
padding-bottom: 15px;
text-align: center;
}
@ -146,7 +145,7 @@ tr:last-child .col2 {
min-width: 200px;
}
#auth input[type=text]:hover{
#auth input[type=text]:hover {
width: 35%;
}
@ -160,6 +159,11 @@ tr:last-child .col2 {
text-align: left;
}
#whitelist .col2 {
text-align: center;
width: 75px;
}
/* Errors */
#error .msg {

View file

@ -6,6 +6,6 @@
<div class="section" id="auth">
<h2 class="title">PAWS</h2>
UvU
PAWS is an extra auth layer for public profiles and posts. The main purpose is to prevent web scrapers from hoarding posts, but it also prevents banned users from viewing toots they shouldn't see.
</div>
{% endblock %}

View file

@ -1,24 +1,21 @@
{% extends "base.html" %}
{% set page = 'Control Panel' %}
{% set page = 'Home' %}
{% block content %}
<div class="section" id="auth">
<h2 class="title">Control Panel</h2>
UvU<br /><br />
<div class="section" id="panel">
<h2 class="title">PAWS</h2>
PAWS is an extra auth layer for public profiles and posts. The main purpose is to prevent web scrapers from hoarding posts, but it also prevents banned users from viewing toots they shouldn't see.<br /><br />
{% if admin %}
<center><h2>Whitelist</h2></center>
<table id="whitelist">
<tr class="header">
<td class="col1">Instance</td>
<td>Remove</td>
</tr>
<tr class="header"><td></td><td></td></tr>
{% if len(whitelist) > 0 %}
{% for instance in whitelist %}
<tr class="instance">
<td class="col1"><a href="https://{{instance}}/about">{{instance}}</a></td>
<td>
<td class="col1"><a href="https://{{instance}}/about" target="_new">{{instance}}</a></td>
<td class="col2">
<form action="https://{{domain}}/paws/action/remove" method="post">
<input name="name" value="{{instance}}" hidden>
<input type="submit" value="X">
@ -32,7 +29,7 @@
<tr class="instance">
<form action="https://{{domain}}/paws/action/add" method="post">
<td class="col1"><input type="text" name="name" placeholder="bofa.lol"></td>
<td>
<td class="col2">
<input type="submit" value="Add">
</td>
</form>

View file

@ -5,14 +5,19 @@ import logging
from aiohttp_jinja2 import render_template as render
from urllib.parse import quote_plus, unquote_plus, urlparse
from datetime import datetime
from .database import pawsdb, trans, query, where, keys, ban_check, get_user, get_toot, admin_check, whitelist
from .functions import error, http_error, fed_domain, domain_check
from .functions import error, http_error, fed_domain, domain_check, css_ts
from .oauth import create_app, login
from .config import MASTOCONFIG, PAWSCONFIG
from .cache import TTLCache
paws_host = PAWSCONFIG['domain']
masto_host = MASTOCONFIG['domain']
async def get_home(request):
'heck2'
@ -27,6 +32,7 @@ async def get_paws(request):
if admin:
whitelist = [line['domain'] for line in pawsdb.whitelist.all()]
whitelist.sort()
else:
whitelist = None
@ -57,7 +63,7 @@ async def post_paws(request):
whitelist(action, parsed_domain)
return aiohttp.web.HTTPFound('/paws')
return aiohttp.web.HTTPFound(f'https://{masto_host}/paws')
async def get_login(request):
@ -67,7 +73,7 @@ async def get_login(request):
numid = random.randint(1*1000000, 10*1000000-1)
if pawsdb.users.get(query.token == token):
return aiohttp.web.HTTPFound(f'/about')
return aiohttp.web.HTTPFound(f'https://{masto_host}/paws')
return render('pages/login.html', request, {'redir': redir, 'numid': numid})
@ -157,37 +163,40 @@ async def get_logout(request):
async def get_style(request):
maxage = 60*60*24*7
response = render('color.css', request, {})
response.headers['Content-Type'] = 'text/css'
response.headers['Content-type'] = 'text/css'
#response.headers['Last-Modified'] = datetime.utcfromtimestamp(css_ts()).strftime('%a, %d %b %Y %H:%M:%S GMT')
response.headers['Cache-Control'] = 'public,max-age={maxage},immutable'
return response
async def get_webfinger(request):
domain = PAWSCONFIG['domain']
res = request.rel_url.query.get('resource')
if not res or res != f'acct:paws@{domain}':
if not res or res != f'acct:paws@{paws_host}':
data = {}
else:
data = {
"aliases": [
f"https://{domain}/paws/actor"
f"https://{paws_host}/paws/actor"
],
"links": [
{
"href": f"https://{domain}/paws/actor",
"href": f"https://{paws_host}/paws/actor",
"rel": "self",
"type": "application/activity+json"
},
{
"href": f"https://{domain}/paws/actor",
"href": f"https://{paws_host}/paws/actor",
"rel": "self",
"type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
}
],
"subject": f"acct:paws@{domain}"
"subject": f"acct:paws@{paws_host}"
}
return aiohttp.web.json_response(data)
@ -195,8 +204,6 @@ async def get_webfinger(request):
async def get_actor(request):
rsakey = keys('default')
HOST = MASTOCONFIG['domain']
host = PAWSCONFIG['domain']
if not rsakey:
return http_error(request, 500, 'Missing actor keys')
@ -208,22 +215,23 @@ async def get_actor(request):
'https://www.w3.org/ns/activitystreams'
],
#'endpoints': {
#'sharedInbox': f'https://{HOST}/inbox'
#'sharedInbox': f'https://{masto_host}/inbox'
#},
#'following': f'https://{host}/paws/following',
#'followers': f'https://{host}/paws/followers',
'inbox': f'https://{host}/paws/inbox',
'name': f'PAWS @ {HOST}',
'inbox': f'https://{paws_host}/paws/inbox',
#'outbox': f'https://{host}/paws/outbox',
'name': f'PAWS @ {masto_host}',
'type': 'Application',
'id': f'https://{host}/paws/actor',
'id': f'https://{paws_host}/paws/actor',
'publicKey': {
'id': f'https://{host}/paws/actor#main-key',
'owner': f'https://{host}/paws/actor',
'id': f'https://{paws_host}/paws/actor#main-key',
'owner': f'https://{paws_host}/paws/actor',
'publicKeyPem': PUBKEY
},
'summary': 'PAWS Actor',
'preferredUsername': 'paws',
'url': f'https://{host}/paws/actor'
'url': f'https://{paws_host}/paws/actor'
}
return aiohttp.web.json_response(data)
@ -232,3 +240,63 @@ async def get_actor(request):
async def post_inbox(request):
return aiohttp.web.json_response('UvU', status=202)
# failed attempt at creating a status
async def get_outbox(request):
if not request.rel_url.query.get('page'):
data = {
"@context": "https://www.w3.org/ns/activitystreams",
"id": f"https://{paws_host}/paws/outbox",
"type": "OrderedCollection",
"totalItems": 1,
"first": f"https://{paws_host}/paws/outbox?page=true",
"last": f"https://{paws_host}/paws/outbox?page=true"
}
else:
data = {
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"ostatus": "http://ostatus.org#",
"atomUri": "ostatus:atomUri",
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"conversation": "ostatus:conversation",
"sensitive": "as:sensitive",
"toot": "http://joinmastodon.org/ns#",
"votersCount": "toot:votersCount",
"blurhash": "toot:blurhash",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
},
"Emoji": "toot:Emoji"
}
],
"id": f"https://{paws_host}/paws/outbox?page=true",
"type": "OrderedCollectionPage",
"partOf": f"https://{paws_host}/paws/outbox",
"orderedItems": [imgay]
}
return aiohttp.web.json_response(data)
async def get_gay(request):
data = imgay
return aiohttp.web.json_response(data)
imgay = {
"id": f"https://{paws_host}/paws/imgay",
"type": "Note",
"published": '2020-01-20T09:45:00Z',
"attributedTo": f"https://{paws_host}/paws/actor",
"content": '<p>im gay</p>',
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
f"https://{paws_host}/paws/actor/followers"
]
}