federated purge of user content

This commit is contained in:
rimu 2024-02-05 19:39:08 +13:00
parent 4f43eec998
commit c51c735038
2 changed files with 128 additions and 5 deletions

View file

@ -14,6 +14,7 @@ from app.models import Post, Community, CommunityMember, User, PostReply, PostVo
Instance, Report, UserBlock, CommunityBan, CommunityJoinRequest, CommunityBlock, Filter
from app.user import bp
from app.user.forms import ProfileForm, SettingsForm, DeleteAccountForm, ReportUserForm, FilterEditForm
from app.user.utils import purge_user_then_delete
from app.utils import get_setting, render_template, markdown_to_html, user_access, markdown_to_text, shorten_string, \
is_image_url, ensure_directory_exists, gibberish, file_get_contents, community_membership, user_filters_home, \
user_filters_posts, user_filters_replies, moderating_communities, joined_communities
@ -449,14 +450,19 @@ def ban_purge_profile(actor):
flash(_('You cannot purge yourself.'), 'error')
else:
user.banned = True
user.deleted = True
db.session.commit()
user.purge_content()
# user.deleted = True # DO NOT set user.deleted until the deletion of their posts has been federated
db.session.commit()
# todo: empty relevant caches
# todo: federate deletion
# federate deletion
if user.is_local():
purge_user_then_delete(user.id)
else:
user.deleted = True
user.delete_dependencies()
user.purge_content()
db.session.commit()
flash(f'{actor} has been banned, deleted and all their content deleted.')
else:

117
app/user/utils.py Normal file
View file

@ -0,0 +1,117 @@
from time import sleep
from flask import current_app, json
from app import celery, db
from app.activitypub.signature import post_request
from app.activitypub.util import default_context
from app.community.util import send_to_remote_instance
from app.models import User, CommunityMember, Community, Instance, Site, utcnow, ActivityPubLog
from app.utils import gibberish, ap_datetime, instance_banned
def purge_user_then_delete(user_id):
if current_app.debug:
purge_user_then_delete_task(user_id)
else:
purge_user_then_delete_task.delay(user_id)
@celery.task
def purge_user_then_delete_task(user_id):
user = User.query.get(user_id)
if user:
# posts
for post in user.posts:
if not post.community.local_only:
delete_json = {
'id': f"https://{current_app.config['SERVER_NAME']}/activities/delete/{gibberish(15)}",
'type': 'Delete',
'actor': user.profile_id(),
'audience': post.community.profile_id(),
'to': [post.community.profile_id(), 'https://www.w3.org/ns/activitystreams#Public'],
'published': ap_datetime(utcnow()),
'cc': [
user.followers_url()
],
'object': post.ap_id,
}
if not post.community.is_local(): # this is a remote community, send it to the instance that hosts it
success = post_request(post.community.ap_inbox_url, delete_json, user.private_key,
user.ap_profile_id + '#main-key')
else: # local community - send it to followers on remote instances, using Announce
announce = {
"id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}",
"type": 'Announce',
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"actor": post.community.ap_profile_id,
"cc": [
post.community.ap_followers_url
],
'@context': default_context(),
'object': delete_json
}
for instance in post.community.following_instances():
if instance.inbox and not instance_banned(instance.domain):
send_to_remote_instance(instance.id, post.community.id, announce)
# unsubscribe
communities = CommunityMember.query.filter_by(user_id=user_id).all()
for membership in communities:
community = Community.query.get(membership.community_id)
unsubscribe_from_community(community, user)
# federate deletion of account
if user.is_local():
instances = Instance.query.all()
site = Site.query.get(1)
payload = {
"@context": default_context(),
"actor": user.ap_profile_id,
"id": f"{user.ap_profile_id}#delete",
"object": user.ap_profile_id,
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Delete"
}
for instance in instances:
if instance.inbox and instance.id != 1:
post_request(instance.inbox, payload, site.private_key,
f"https://{current_app.config['SERVER_NAME']}#main-key")
sleep(100) # wait a while for any related activitypub traffic to die down.
user.deleted = True
user.delete_dependencies()
user.purge_content()
db.session.commit()
def unsubscribe_from_community(community, user):
undo_id = f"https://{current_app.config['SERVER_NAME']}/activities/undo/" + gibberish(15)
follow = {
"actor": f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}",
"to": [community.ap_profile_id],
"object": community.ap_profile_id,
"type": "Follow",
"id": f"https://{current_app.config['SERVER_NAME']}/activities/follow/{gibberish(15)}"
}
undo = {
'actor': user.profile_id(),
'to': [community.ap_profile_id],
'type': 'Undo',
'id': undo_id,
'object': follow
}
activity = ActivityPubLog(direction='out', activity_id=undo_id, activity_type='Undo',
activity_json=json.dumps(undo), result='processing')
db.session.add(activity)
db.session.commit()
post_request(community.ap_inbox_url, undo, user.private_key, user.profile_id() + '#main-key')
activity.result = 'success'
db.session.commit()