2024-01-27 22:10:51 +00:00
|
|
|
"""Opts a user out and deletes their bridged profiles in other networks.
|
|
|
|
|
|
|
|
https://github.com/snarfed/bridgy-fed/issues/783
|
|
|
|
|
|
|
|
Usage: opt_out.py [PROTOCOL] [USER_ID] [EXTRA_TARGETS ...]
|
|
|
|
|
|
|
|
PROTOCOL: protocol label, eg web, activitypub, atproto
|
|
|
|
USER_ID: key id of the user entity
|
|
|
|
EXTRA_TARGETS: bridged profiles will also be deleted here
|
|
|
|
|
|
|
|
Run with:
|
|
|
|
|
|
|
|
source local/bin/activate.csh
|
|
|
|
env PYTHONPATH=. GOOGLE_APPLICATION_CREDENTIALS=service_account_creds.json \
|
|
|
|
python scripts/opt_out.py ...
|
|
|
|
"""
|
|
|
|
import logging
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from google.cloud import ndb
|
|
|
|
from oauth_dropins.webutil import appengine_info
|
|
|
|
appengine_info.DEBUG = False
|
|
|
|
from oauth_dropins.webutil import appengine_config, flask_util, util
|
|
|
|
|
|
|
|
import ids
|
|
|
|
from models import Object, Target
|
|
|
|
import protocol
|
|
|
|
import activitypub, atproto, web
|
|
|
|
from app import app
|
|
|
|
|
|
|
|
appengine_config.error_reporting_client.host = 'localhost:9999'
|
|
|
|
appengine_config.error_reporting_client.secure = False
|
|
|
|
|
|
|
|
# logging.basicConfig(level=logging.DEBUG)
|
|
|
|
|
|
|
|
|
|
|
|
# Includes top 20-40 each from fedidb.org and fediverse.observer on 2024-01-23
|
|
|
|
AP_BASE_TARGETS = [
|
|
|
|
# Diaspora
|
|
|
|
# 'https://joindiaspora.com/',
|
|
|
|
# 'https://diasp.org/',
|
|
|
|
|
|
|
|
# Friendica
|
|
|
|
'https://venera.social/inbox',
|
|
|
|
|
|
|
|
# kbin (not sharedInbox)
|
|
|
|
'https://kbin.social/i/inbox',
|
|
|
|
|
|
|
|
# Lemmy (not sharedInbox)
|
|
|
|
'https://alien.top/inbox',
|
|
|
|
# 'https://enterprise.lemmy.ml/inbox',
|
|
|
|
'https://lemmy.ml/inbox',
|
|
|
|
'https://lemmy.world/inbox',
|
|
|
|
'https://pasta.faith/inbox',
|
|
|
|
|
|
|
|
# Mastodon
|
|
|
|
'https://baraag.net/inbox',
|
|
|
|
'https://c.im/inbox',
|
|
|
|
'https://daystorm.netz.org/inbox',
|
|
|
|
'https://fosstodon.org/inbox',
|
|
|
|
'https://gc2.jp/inbox',
|
|
|
|
'https://hachyderm.io/inbox',
|
|
|
|
'https://indieweb.social/inbox',
|
|
|
|
'https://infosec.exchange/inbox',
|
|
|
|
'https://mas.to/inbox',
|
|
|
|
'https://masto.ai/inbox',
|
|
|
|
'https://mastodon.cloud/inbox',
|
|
|
|
'https://mastodon.online/inbox',
|
|
|
|
'https://mastodon.sdf.org/inbox',
|
|
|
|
'https://mastodon.social/inbox',
|
|
|
|
'https://mastodon.top/inbox',
|
|
|
|
'https://mastodon.uno/inbox',
|
|
|
|
'https://mastodon.world/inbox',
|
|
|
|
'https://mastodonapp.uk/inbox',
|
|
|
|
'https://mstdn.jp/inbox',
|
|
|
|
'https://mstdn.social/inbox',
|
|
|
|
'https://pawoo.net/inbox',
|
|
|
|
'https://pravda.me/inbox',
|
|
|
|
'https://r-sauna.fi/inbox',
|
|
|
|
'https://techhub.social/inbox',
|
|
|
|
'https://universeodon.com/inbox',
|
|
|
|
|
|
|
|
# Misskey
|
|
|
|
'https://misskey.io/inbox',
|
|
|
|
|
|
|
|
# micro.blog
|
|
|
|
'https://micro.blog/activitypub/shared/inbox',
|
|
|
|
|
|
|
|
# PixelFed (not sharedInbox)
|
|
|
|
'https://pixelfed.social/i/actor/inbox',
|
|
|
|
|
|
|
|
# Twitter bridge
|
|
|
|
# 'https://bird.makeup/inbox',
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def run():
|
|
|
|
assert len(sys.argv) >= 3
|
|
|
|
proto, user_id, extra_targets = sys.argv[1], sys.argv[2], sys.argv[3:]
|
|
|
|
|
|
|
|
# can't do get_by_id because they might be opted out
|
|
|
|
from_proto = protocol.PROTOCOLS[proto]
|
|
|
|
kind = from_proto._get_kind()
|
|
|
|
user = ndb.Key(kind, user_id).get()
|
|
|
|
assert user, f'{kind} {user_id} not found'
|
|
|
|
|
|
|
|
if from_proto is web.Web:
|
|
|
|
user_id = user.web_url()
|
|
|
|
to_proto = activitypub.ActivityPub # TODO: generalize
|
|
|
|
|
|
|
|
to_user_id = ids.translate_user_id(id=user_id, from_proto=from_proto,
|
|
|
|
to_proto=to_proto)
|
|
|
|
delete_id = f'{to_user_id}#bridgy-fed-delete-{util.now().isoformat()}'
|
|
|
|
obj = Object(id=delete_id, status='new', source_protocol=from_proto.LABEL,
|
|
|
|
# use as2 so that we don't convert. if we try to convert an opted
|
|
|
|
# out user's id, we choke. should probably relax that.
|
|
|
|
as2={
|
2024-01-29 02:23:10 +00:00
|
|
|
'type': 'Delete',
|
2024-01-27 22:10:51 +00:00
|
|
|
'id': delete_id,
|
|
|
|
# if the actor is already deleted on this instance, it may
|
|
|
|
# return 502 here because it no longer has the actor's
|
|
|
|
# public key, so it can't verify the HTTP Sig. (eg Mastodon
|
|
|
|
# does this; it uses LD Sigs for its actor deletes
|
|
|
|
# instead.)
|
|
|
|
#
|
|
|
|
# an alternative is to use the instance actor:
|
|
|
|
#
|
|
|
|
# activitypub.instance_actor().key.urlsafe()
|
|
|
|
#
|
|
|
|
# ...which gets accepted, but I'm not sure all
|
|
|
|
# implementations accept the instance actor as authorized
|
|
|
|
# to delete a different actor.
|
|
|
|
'actor': to_user_id,
|
|
|
|
'object': to_user_id,
|
2024-02-05 22:06:26 +00:00
|
|
|
},
|
|
|
|
our_as1={
|
|
|
|
'objectType': 'activity',
|
|
|
|
'verb': 'delete',
|
|
|
|
'id': delete_id,
|
|
|
|
'actor': user_id,
|
|
|
|
'object': user_id,
|
2024-01-27 22:10:51 +00:00
|
|
|
})
|
|
|
|
obj.put()
|
|
|
|
|
2024-02-05 22:06:26 +00:00
|
|
|
if user.manual_opt_out:
|
|
|
|
user.manual_opt_out = False # needed for key_for in user.targets() below
|
|
|
|
user.put()
|
|
|
|
|
|
|
|
targets = [Target(uri=t, protocol='activitypub')
|
|
|
|
for t in AP_BASE_TARGETS + extra_targets] \
|
|
|
|
+ list(user.targets(obj).keys())
|
|
|
|
obj.undelivered = targets
|
|
|
|
|
2024-01-27 22:10:51 +00:00
|
|
|
for target in targets:
|
2024-02-05 22:06:26 +00:00
|
|
|
assert util.is_web(target.uri), f'Non-URL target: {target.uri}'
|
2024-01-27 22:10:51 +00:00
|
|
|
params = {
|
|
|
|
'protocol': to_proto.LABEL,
|
|
|
|
'url': target.uri,
|
|
|
|
'obj': obj.key.urlsafe(),
|
|
|
|
'user': user.key.urlsafe(),
|
|
|
|
'force': 'true',
|
|
|
|
}
|
|
|
|
with app.test_request_context('/queue/send', base_url='https://fed.brid.gy/',
|
|
|
|
data=params, headers={
|
|
|
|
flask_util.CLOUD_TASKS_QUEUE_HEADER: '',
|
|
|
|
}):
|
|
|
|
protocol.send_task()
|
|
|
|
|
2024-02-05 22:06:26 +00:00
|
|
|
if not user.manual_opt_out:
|
|
|
|
user.manual_opt_out = True
|
|
|
|
user.put()
|
|
|
|
|
2024-01-27 22:10:51 +00:00
|
|
|
|
|
|
|
with appengine_config.ndb_client.context(), \
|
|
|
|
app.test_request_context(base_url='https://fed.brid.gy/'):
|
|
|
|
run()
|