Compare commits

..

No commits in common. "10282560654c975c5a77aead3017035406372e53" and "c41cd6e015e789ea81079511820cf64f2ee6dfd0" have entirely different histories.

6 changed files with 34 additions and 138 deletions

View file

@ -177,7 +177,7 @@ class Connection(tinysql.ConnectionMixin):
def get_hostnames(self): def get_hostnames(self):
return tuple(row.domain for row in self.get_instances()) return tuple(row.domain for row in self.select('instances'))
def get_instance(self, data): def get_instance(self, data):
@ -185,8 +185,7 @@ class Connection(tinysql.ConnectionMixin):
data = data.split('#', 1)[0] data = data.split('#', 1)[0]
query = 'SELECT * FROM instances WHERE domain = :data OR actor = :data OR inbox = :data' query = 'SELECT * FROM instances WHERE domain = :data OR actor = :data OR inbox = :data'
row = self.execute(query, dict(data=data), table='instances').one() return self.execute(query, dict(data=data), table='instances').one()
return row if row.joined else None
def get_instances(self): def get_instances(self):
@ -195,16 +194,11 @@ class Connection(tinysql.ConnectionMixin):
def get_request(self, domain): def get_request(self, domain):
for instance in self.get_requests(): return self.select('instances', domain=domain, joined=None).one()
if instance.domain == domain:
return instance
raise KeyError(domain)
def get_requests(self): def get_requests(self):
query = 'SELECT * FROM instances WHERE joined IS NULL' self.select('instances', joined=None).all()
return self.execute(query, table='instances').all()
def get_whitelist(self): def get_whitelist(self):

View file

@ -212,8 +212,6 @@ class HttpClient(AppBase):
return await self.get(nodeinfo_url, loads=Nodeinfo.new_from_json) or False return await self.get(nodeinfo_url, loads=Nodeinfo.new_from_json) or False
## http client methods can't be called directly from manage.py,
## so here's some wrapper functions
async def get(*args, **kwargs): async def get(*args, **kwargs):
async with HttpClient() as client: async with HttpClient() as client:
return await client.get(*args, **kwargs) return await client.get(*args, **kwargs)

View file

@ -6,7 +6,6 @@ import logging
import platform import platform
import yaml import yaml
from datetime import datetime
from urllib.parse import urlparse from urllib.parse import urlparse
from . import __version__ from . import __version__
@ -385,77 +384,6 @@ def cli_inbox_remove(domain):
return click.echo(f'Error: Inbox does not exist: {domain}') return click.echo(f'Error: Inbox does not exist: {domain}')
@cli.group('request')
def cli_request():
'Manage follow requests'
@cli_request.command('list')
def cli_request_list():
'List all the current follow requests'
click.echo('Follow requests:')
with app.database.session as s:
for row in s.get_requests():
click.echo(f'- {row.domain}')
@cli_request.command('approve')
@click.argument('domain')
def cli_request_approve(domain):
'Approve a follow request'
with app.database.session as s:
try:
instance = s.get_request(domain)
except KeyError:
return click.echo(f'No request for domain exists: {domain}')
data = {'joined': datetime.now()}
s.update('instances', data, id=instance.id)
asyncio.run(post(
instance.inbox,
Message.new_response(
host = app.config.host,
actor = instance.actor,
followid = instance.followid,
accept = True
)
))
return click.echo(f'Accepted follow request for domain: {domain}')
@cli_request.command('deny')
@click.argument('domain')
def cli_request_deny(domain):
'Deny a follow request'
with app.database.session as s:
try:
instance = s.get_request(domain)
except KeyError:
return click.echo(f'No request for domain exists: {domain}')
s.delete_instance(domain)
asyncio.run(post(
instance.inbox,
Message.new_response(
host = app.config.host,
actor = instance.actor,
followid = instance.followid,
accept = False
)
))
return click.echo(f'Denied follow request for domain: {domain}')
@cli.group('instance') @cli.group('instance')
def cli_instance(): def cli_instance():
'Manage instance bans' 'Manage instance bans'

View file

@ -192,15 +192,14 @@ class DotDict(dict):
class Message(DotDict): class Message(DotDict):
@classmethod @classmethod
def new_actor(cls, host, pubkey, name=None, description=None, locked=False): def new_actor(cls, host, pubkey, description=None):
return cls({ return cls({
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': f'https://{host}/actor', 'id': f'https://{host}/actor',
'type': 'Application', 'type': 'Application',
'preferredUsername': 'relay', 'preferredUsername': 'relay',
'name': name or 'ActivityRelay', 'name': 'ActivityRelay',
'summary': description or 'ActivityRelay bot', 'summary': description or 'ActivityRelay bot',
'manuallyApprovesFollowers': locked,
'followers': f'https://{host}/followers', 'followers': f'https://{host}/followers',
'following': f'https://{host}/following', 'following': f'https://{host}/following',
'inbox': f'https://{host}/inbox', 'inbox': f'https://{host}/inbox',

View file

@ -26,11 +26,6 @@ async def handle_relay(request, s):
logging.verbose(f'already relayed {request.message.objectid}') logging.verbose(f'already relayed {request.message.objectid}')
return return
if request.message.get('to') != ['https://www.w3.org/ns/activitystreams#Public']:
logging.verbose('Message was not public')
logging.verbose(request.message.get('to'))
return
message = Message.new_announce( message = Message.new_announce(
host = request.config.host, host = request.config.host,
object = request.message.objectid object = request.message.objectid
@ -73,17 +68,17 @@ async def handle_follow(request, s):
## reject if the actor isn't whitelisted while the whiltelist is enabled ## reject if the actor isn't whitelisted while the whiltelist is enabled
if s.get_config('whitelist') and not s.get_whitelist(request.actor.domain): if s.get_config('whitelist') and not s.get_whitelist(request.actor.domain):
logging.verbose(f'Rejected actor for not being in the whitelist: {request.actor.id}') logging.verbose(f'Rejected actor for not being in the whitelist: {request.actor.id}')
approve = False accept = False
## reject if software used by actor is banned ## reject if software used by actor is banned
if s.get_ban('software', software): if s.get_banned_software(software):
logging.verbose(f'Rejected follow from actor for using specific software: actor={request.actor.id}, software={software}') logging.verbose(f'Rejected follow from actor for using specific software: actor={request.actor.id}, software={software}')
approve = False accept = False
## reject if the actor is not an instance actor ## reject if the actor is not an instance actor
if person_check(request.actor, software): if person_check(request.actor, software):
logging.verbose(f'Non-application actor tried to follow: {request.actor.id}') logging.verbose(f'Non-application actor tried to follow: {request.actor.id}')
approve = False accept = False
if approve: if approve:
if not request.instance: if not request.instance:
@ -94,7 +89,7 @@ async def handle_follow(request, s):
actor_data = request.actor, actor_data = request.actor,
software = software, software = software,
followid = request.message.id, followid = request.message.id,
accept = not s.get_config('require_approval') accept = s.get_config('require_approval')
) )
if s.get_config('require_approval'): if s.get_config('require_approval'):
@ -106,12 +101,11 @@ async def handle_follow(request, s):
followid = request.message.id followid = request.message.id
) )
# Rejects don't seem to work right with mastodon
request.app.push_message( request.app.push_message(
request.actor.inbox, request.actor.shared_inbox,
Message.new_response( Message.new_response(
host = request.config.host, host = request.config.host,
actor = request.message.actorid, actor = request.actor.id,
followid = request.message.id, followid = request.message.id,
accept = approve accept = approve
) )
@ -142,27 +136,16 @@ async def handle_undo(request, s):
if request.message.object.type != 'Follow': if request.message.object.type != 'Follow':
return await handle_forward(request) return await handle_forward(request)
instance_follow = request.instance.followid
message_follow = request.message.object.id
if person_check(request.actor, request.instance.software):
return logging.verbose(f'Non-application actor tried to unfollow: {request.actor.id}')
if instance_follow and instance_follow != message_follow:
return logging.verbose(f'Followid does not match: {instance_follow}, {message_follow}')
s.delete('instances', id=request.instance.id) s.delete('instances', id=request.instance.id)
logging.verbose(f'Removed inbox: {request.instance.inbox}')
if request.instance.software != 'mastodon': request.app.push_message(
request.app.push_message( request.actor.shared_inbox,
request.actor.shared_inbox, Message.new_unfollow(
Message.new_unfollow( host = request.config.host,
host = request.config.host, actor = request.actor.id,
actor = request.actor.id, follow = request.message
follow = request.message
)
) )
)
processors = { processors = {
@ -180,25 +163,22 @@ async def run_processor(request):
return return
with request.database.session as s: with request.database.session as s:
if request.instance: new_data = {}
new_data = {}
if not request.instance.software: if request.instance and not request.instance.software:
logging.verbose(f'Fetching nodeinfo for instance: {request.instance.domain}') nodeinfo = await request.app.client.fetch_nodeinfo(request.instance.domain)
nodeinfo = await request.app.client.fetch_nodeinfo(request.instance.domain)
if nodeinfo: if nodeinfo:
new_data['software'] = nodeinfo.sw_name new_data['software'] = nodeinfo.sw_name
if not request.instance.actor: if not request.instance.actor:
logging.verbose(f'Fetching actor for instance: {request.instance.domain}') new_data['actor'] = request.signature.keyid.split('#', 1)[0]
new_data['actor'] = request.signature.keyid.split('#', 1)[0]
if not request.instance.actor_data: if not request.instance.actor_data:
new_data['actor_data'] = request.actor new_data['actor_data'] = request.actor
if new_data: if new_data:
s.put_instance(request.actor.domain, **new_data) s.put_instance(request.actor.domain, **new_data)
logging.verbose(f'New "{request.message.type}" from actor: {request.actor.id}') logging.verbose(f'New "{request.message.type}" from actor: {request.actor.id}')
return await processors[request.message.type](request, s) return await processors[request.message.type](request, s)

View file

@ -68,13 +68,10 @@ a:hover {{ color: #8AF; }}
@register_route('GET', '/actor', '/inbox') @register_route('GET', '/actor', '/inbox')
async def actor(request, s): async def actor(request):
data = Message.new_actor( data = Message.new_actor(
host = request.config.host, host = request.config.host,
pubkey = request.app.signer.pubkey, pubkey = request.app.signer.pubkey
name = s.get_config('name'),
description = s.get_config('description'),
locked = s.get_config('require_approval')
) )
return Response.new(data, ctype='activity') return Response.new(data, ctype='activity')
@ -138,7 +135,7 @@ async def inbox(request, s):
return Response.new_error(401, str(e), 'json') return Response.new_error(401, str(e), '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 request.message.type != 'Follow' and (not request.instance or not request.instance.joined): if request.message.type != 'Follow' and not request.instance:
logging.verbose(f'Rejected actor for trying to post while not following: {request.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')