mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-12-23 03:31:07 +00:00
prevent old unfollows from booting instances
This commit is contained in:
parent
4d121adaa2
commit
dcb7980c50
|
@ -6,10 +6,15 @@ from Crypto.PublicKey import RSA
|
|||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
class RelayDatabase:
|
||||
class RelayDatabase(dict):
|
||||
def __init__(self, config):
|
||||
dict.__init__(self, {
|
||||
'relay-list': {},
|
||||
'private-key': None,
|
||||
'version': 1
|
||||
})
|
||||
|
||||
self.config = config
|
||||
self.data = None
|
||||
self.PRIVKEY = None
|
||||
|
||||
|
||||
|
@ -25,26 +30,23 @@ class RelayDatabase:
|
|||
|
||||
@property
|
||||
def privkey(self):
|
||||
try:
|
||||
return self.data['private-key']
|
||||
|
||||
except KeyError:
|
||||
return False
|
||||
return self['private-key']
|
||||
|
||||
|
||||
@property
|
||||
def hostnames(self):
|
||||
return [urlparse(inbox).hostname for inbox in self.inboxes]
|
||||
return tuple(self['relay-list'].keys())
|
||||
|
||||
|
||||
@property
|
||||
def inboxes(self):
|
||||
return self.data.get('relay-list', [])
|
||||
return tuple(data['inbox'] for data in self['relay-list'].values())
|
||||
return self['relay-list']
|
||||
|
||||
|
||||
def generate_key(self):
|
||||
self.PRIVKEY = RSA.generate(4096)
|
||||
self.data['private-key'] = self.PRIVKEY.exportKey('PEM').decode('utf-8')
|
||||
self['private-key'] = self.PRIVKEY.exportKey('PEM').decode('utf-8')
|
||||
|
||||
|
||||
def load(self):
|
||||
|
@ -52,14 +54,31 @@ class RelayDatabase:
|
|||
|
||||
try:
|
||||
with self.config.db.open() as fd:
|
||||
self.data = json.load(fd)
|
||||
data = json.load(fd)
|
||||
|
||||
key = self.data.pop('actorKeys', None)
|
||||
self['version'] = data.get('version', None)
|
||||
self['private-key'] = data.get('private-key')
|
||||
|
||||
if key:
|
||||
self.data['private-key'] = key.get('privateKey')
|
||||
if self['version'] == None:
|
||||
self['version'] = 1
|
||||
|
||||
if 'actorKeys' in data:
|
||||
self['private-key'] = data['actorKeys']['privateKey']
|
||||
|
||||
for item in data.get('relay-list', []):
|
||||
domain = urlparse(item).hostname
|
||||
self['relay-list'][domain] = {
|
||||
'inbox': item,
|
||||
'followid': None
|
||||
}
|
||||
|
||||
else:
|
||||
self['relay-list'] = data.get('relay-list', {})
|
||||
|
||||
for domain in self['relay-list'].keys():
|
||||
if self.config.is_banned(domain) or (self.config.whitelist_enabled and not self.config.is_whitelisted(domain)):
|
||||
self.del_inbox(domain)
|
||||
|
||||
self.data.pop('actors', None)
|
||||
new_db = False
|
||||
|
||||
except FileNotFoundError:
|
||||
|
@ -69,14 +88,6 @@ class RelayDatabase:
|
|||
if self.config.db.stat().st_size > 0:
|
||||
raise e from None
|
||||
|
||||
if not self.data:
|
||||
logging.info('No database was found. Making a new one.')
|
||||
self.data = {}
|
||||
|
||||
for inbox in self.inboxes:
|
||||
if self.config.is_banned(inbox) or (self.config.whitelist_enabled and not self.config.is_whitelisted(inbox)):
|
||||
self.del_inbox(inbox)
|
||||
|
||||
if not self.privkey:
|
||||
logging.info("No actor keys present, generating 4096-bit RSA keypair.")
|
||||
self.generate_key()
|
||||
|
@ -90,34 +101,57 @@ class RelayDatabase:
|
|||
|
||||
def save(self):
|
||||
with self.config.db.open('w') as fd:
|
||||
data = {
|
||||
'relay-list': self.inboxes,
|
||||
'private-key': self.privkey
|
||||
}
|
||||
|
||||
json.dump(data, fd, indent=4)
|
||||
json.dump(self, fd, indent=4)
|
||||
|
||||
|
||||
def get_inbox(self, domain):
|
||||
def get_inbox(self, domain, fail=False):
|
||||
if domain.startswith('http'):
|
||||
domain = urlparse(domain).hostname
|
||||
|
||||
for inbox in self.inboxes:
|
||||
if domain == urlparse(inbox).hostname:
|
||||
return inbox
|
||||
if domain not in self['relay-list']:
|
||||
if fail:
|
||||
raise KeyError(domain)
|
||||
|
||||
return
|
||||
|
||||
return self['relay-list'][domain]
|
||||
|
||||
|
||||
def add_inbox(self, inbox):
|
||||
assert inbox.startswith('https')
|
||||
assert not self.get_inbox(inbox)
|
||||
def add_inbox(self, inbox, followid=None, fail=False):
|
||||
assert inbox.startswith('https'), 'Inbox must be a url'
|
||||
domain = urlparse(inbox).hostname
|
||||
|
||||
self.data['relay-list'].append(inbox)
|
||||
if self.get_inbox(domain):
|
||||
if fail:
|
||||
raise KeyError(domain)
|
||||
|
||||
return False
|
||||
|
||||
self['relay-list'][domain] = {
|
||||
'domain': domain,
|
||||
'inbox': inbox,
|
||||
'followid': followid
|
||||
}
|
||||
|
||||
logging.verbose(f'Added inbox to database: {inbox}')
|
||||
return self['relay-list'][domain]
|
||||
|
||||
|
||||
def del_inbox(self, inbox_url):
|
||||
inbox = self.get_inbox(inbox_url)
|
||||
def del_inbox(self, domain, followid=None, fail=False):
|
||||
data = self.get_inbox(domain, fail=True)
|
||||
|
||||
if not inbox:
|
||||
raise KeyError(inbox_url)
|
||||
if not data['followid'] or not followid or data['followid'] == followid:
|
||||
del self['relay-list'][data['domain']]
|
||||
logging.verbose(f'Removed inbox from database: {data["inbox"]}')
|
||||
return True
|
||||
|
||||
self.data['relay-list'].remove(inbox)
|
||||
if fail:
|
||||
raise ValueError('Follow IDs do not match')
|
||||
|
||||
logging.debug(f'Follow ID does not match: db = {data["followid"]}, object = {followid}')
|
||||
return False
|
||||
|
||||
|
||||
def set_followid(self, domain, followid):
|
||||
data = self.get_inbox(domain, fail=True)
|
||||
data['followid'] = followid
|
||||
|
|
|
@ -100,14 +100,12 @@ def cli_inbox_unfollow(actor):
|
|||
if not actor.startswith('http'):
|
||||
actor = f'https://{actor}/actor'
|
||||
|
||||
if not database.get_inbox(actor):
|
||||
return click.echo(f'Error: Not following actor: {actor}')
|
||||
if database.del_inbox(actor):
|
||||
database.save()
|
||||
run_in_loop(misc.unfollow_remote_actor, actor)
|
||||
return click.echo(f'Sent unfollow message to: {actor}')
|
||||
|
||||
database.del_inbox(actor)
|
||||
database.save()
|
||||
|
||||
run_in_loop(misc.unfollow_remote_actor, actor)
|
||||
click.echo(f'Sent unfollow message to: {actor}')
|
||||
return click.echo(f'Error: Not following actor: {actor}')
|
||||
|
||||
|
||||
@cli_inbox.command('add')
|
||||
|
@ -121,17 +119,14 @@ def cli_inbox_add(inbox):
|
|||
if not inbox.startswith('http'):
|
||||
inbox = f'https://{inbox}/inbox'
|
||||
|
||||
if database.get_inbox(inbox):
|
||||
click.echo(f'Error: Inbox already in database: {inbox}')
|
||||
return
|
||||
|
||||
if config.is_banned(inbox):
|
||||
click.echo(f'Error: Refusing to add banned inbox: {inbox}')
|
||||
return
|
||||
return click.echo(f'Error: Refusing to add banned inbox: {inbox}')
|
||||
|
||||
database.add_inbox(inbox)
|
||||
database.save()
|
||||
click.echo(f'Added inbox to the database: {inbox}')
|
||||
if database.add_inbox(inbox):
|
||||
database.save()
|
||||
return click.echo(f'Added inbox to the database: {inbox}')
|
||||
|
||||
click.echo(f'Error: Inbox already in database: {inbox}')
|
||||
|
||||
|
||||
@cli_inbox.command('remove')
|
||||
|
@ -140,14 +135,17 @@ def cli_inbox_remove(inbox):
|
|||
'Remove an inbox from the database'
|
||||
|
||||
database = app['database']
|
||||
dbinbox = database.get_inbox(inbox)
|
||||
|
||||
if not dbinbox:
|
||||
try:
|
||||
dbinbox = database.get_inbox(inbox, fail=True)
|
||||
|
||||
except KeyError:
|
||||
click.echo(f'Error: Inbox does not exist: {inbox}')
|
||||
return
|
||||
|
||||
database.del_inbox(dbinbox)
|
||||
database.del_inbox(dbinbox['domain'])
|
||||
database.save()
|
||||
|
||||
click.echo(f'Removed inbox from the database: {inbox}')
|
||||
|
||||
|
||||
|
@ -174,13 +172,14 @@ def cli_instance_ban(target):
|
|||
|
||||
config = app['config']
|
||||
database = app['database']
|
||||
inbox = database.get_inbox(target)
|
||||
|
||||
if target.startswith('http'):
|
||||
target = urlparse(target).hostname
|
||||
|
||||
if config.ban_instance(target):
|
||||
config.save()
|
||||
|
||||
if inbox:
|
||||
database.del_inbox(inbox)
|
||||
if database.del_inbox(target):
|
||||
database.save()
|
||||
|
||||
click.echo(f'Banned instance: {target}')
|
||||
|
@ -321,16 +320,15 @@ def cli_whitelist_remove(instance):
|
|||
|
||||
config = app['config']
|
||||
database = app['database']
|
||||
inbox = database.get_inbox(instance)
|
||||
|
||||
if not config.del_whitelist(instance):
|
||||
return click.echo(f'Instance not in the whitelist: {instance}')
|
||||
|
||||
config.save()
|
||||
|
||||
if inbox and config.whitelist_enabled:
|
||||
database.del_inbox(inbox)
|
||||
database.save()
|
||||
if config.whitelist_enabled:
|
||||
if database.del_inbox(inbox):
|
||||
database.save()
|
||||
|
||||
click.echo(f'Removed instance from the whitelist: {instance}')
|
||||
|
||||
|
|
|
@ -255,6 +255,12 @@ async def request(uri, data=None, force=False, sign_headers=True, activity=True)
|
|||
headers.update(signing_headers)
|
||||
|
||||
try:
|
||||
if data:
|
||||
logging.verbose(f'Sending "{action}" to inbox: {uri}')
|
||||
|
||||
else:
|
||||
logging.verbose(f'Sending GET request to url: {uri}')
|
||||
|
||||
async with ClientSession(trace_configs=http_debug()) as session, app['semaphore']:
|
||||
async with session.request(method, uri, headers=headers, data=data) as resp:
|
||||
## aiohttp has been known to leak if the response hasn't been read,
|
||||
|
|
|
@ -60,11 +60,13 @@ async def handle_follow(actor, data, request):
|
|||
database = app['database']
|
||||
|
||||
inbox = misc.get_actor_inbox(actor)
|
||||
dbinbox = database.get_inbox(inbox)
|
||||
|
||||
if inbox not in database.inboxes:
|
||||
database.add_inbox(inbox)
|
||||
if not database.add_inbox(inbox, data['id']):
|
||||
database.set_followid(inbox, data['id'])
|
||||
database.save()
|
||||
asyncio.ensure_future(misc.follow_remote_actor(actor['id']))
|
||||
|
||||
asyncio.ensure_future(misc.follow_remote_actor(actor['id']))
|
||||
|
||||
message = {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
|
@ -92,12 +94,11 @@ async def handle_undo(actor, data, request):
|
|||
return await handle_forward(actor, data, request)
|
||||
|
||||
database = app['database']
|
||||
inbox = database.get_inbox(actor['id'])
|
||||
objectid = misc.distill_object_id(data)
|
||||
|
||||
if not inbox:
|
||||
if not database.del_inbox(actor['id'], objectid):
|
||||
return
|
||||
|
||||
database.del_inbox(inbox)
|
||||
database.save()
|
||||
|
||||
await misc.unfollow_remote_actor(actor['id'])
|
||||
|
|
|
@ -37,7 +37,7 @@ a:hover {{ color: #8AF; }}
|
|||
<p>You may subscribe to this relay with the address: <a href="https://{host}/actor">https://{host}/actor</a></p>
|
||||
<p>To host your own relay, you may download the code at this address: <a href="https://git.pleroma.social/pleroma/relay">https://git.pleroma.social/pleroma/relay</a></p>
|
||||
<br><p>List of {count} registered instances:<br>{targets}</p>
|
||||
</body></html>""".format(host=request.host, note=app['config'].note, targets=targets, count=len(app['database'].inboxes))
|
||||
</body></html>""".format(host=request.host, note=app['config'].note, targets=targets, count=len(app['database'].hostnames))
|
||||
|
||||
return Response(
|
||||
status = 200,
|
||||
|
@ -89,11 +89,11 @@ async def inbox(request):
|
|||
actor_id = data['actor']
|
||||
actor_domain = urlparse(actor_id).hostname
|
||||
|
||||
## reject if there is no actor in the message
|
||||
except KeyError:
|
||||
logging.verbose('actor not in data')
|
||||
raise HTTPUnauthorized(body='no actor in message')
|
||||
|
||||
## reject if there is no actor in the message
|
||||
except:
|
||||
traceback.print_exc()
|
||||
logging.verbose('Failed to parse inbox message')
|
||||
|
|
Loading…
Reference in a new issue