From 0220739d16f01a2888eb9a9af4e8647585955c8c Mon Sep 17 00:00:00 2001 From: freamon Date: Sun, 20 Oct 2024 23:22:56 +0000 Subject: [PATCH 1/5] post-reply soft-deletion: add info to activitypublog for deletions --- app/activitypub/util.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/activitypub/util.py b/app/activitypub/util.py index 4eac7f9b..2f6cd1e6 100644 --- a/app/activitypub/util.py +++ b/app/activitypub/util.py @@ -1314,8 +1314,9 @@ def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id deletor = find_actor_or_create(user_ap_id) community = find_actor_or_create(community_ap_id, community_only=True) to_delete = find_liked_object(to_be_deleted_ap_id) + aplog = ActivityPubLog.query.get(aplog_id) + if to_delete and to_delete.deleted: - aplog = ActivityPubLog.query.get(aplog_id) if aplog: aplog.result = 'ignored' aplog.exception_message = 'Activity about local content which is already deleted' @@ -1345,6 +1346,16 @@ def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id add_to_modlog_activitypub('delete_post_reply', deletor, community_id=community.id, link_text=f'comment on {shorten_string(to_delete.post.title)}', link=f'post/{to_delete.post.id}#comment_{to_delete.id}') + if aplog: + aplog.result = 'success' + else: + if aplog: + aplog.result = 'failure' + aplog.exception_message = 'Deletor did not have permission' + else: + if aplog: + aplog.result = 'failure' + aplog.exception_message = 'Unable to resolve deletor, community, or target' def restore_post_or_comment(object_json): From 1a0ad888db345d9e44fe00c92ee2ac2c97a22aa2 Mon Sep 17 00:00:00 2001 From: freamon Date: Mon, 21 Oct 2024 17:54:20 +0000 Subject: [PATCH 2/5] post-reply soft-deletion: remote users in local communities --- app/activitypub/routes.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/app/activitypub/routes.py b/app/activitypub/routes.py index e31a6ce6..2cbb6e06 100644 --- a/app/activitypub/routes.py +++ b/app/activitypub/routes.py @@ -1107,18 +1107,21 @@ def process_inbox_request(request_json, activitypublog_id, ip_address): # Delete PostReply reply = PostReply.query.filter_by(ap_id=ap_id).first() if reply: - if can_delete(request_json['actor'], reply): - reply.body_html = '

deleted

' - reply.body = 'deleted' - if not reply.author.bot: - reply.post.reply_count -= 1 - reply.deleted = True - announce_activity_to_followers(reply.community, reply.author, request_json) - reply.author.post_reply_count -= 1 - db.session.commit() - activity_log.result = 'success' + deletor = find_actor_or_create(request_json['actor'], create_if_not_found=False) + if deletor: + if reply.author.id == deletor.id or reply.community.is_moderator(deletor) or reply.community.is_instance_admin(deletor): + reply.deleted = True + reply.deleted_by = deletor.id + if not reply.author.bot: + reply.post.reply_count -= 1 + reply.author.post_reply_count -= 1 + announce_activity_to_followers(reply.community, reply.author, request_json) + db.session.commit() + activity_log.result = 'success' + else: + activity_log.exception_message = 'Delete attempt denied' else: - activity_log.exception_message = 'Delete attempt denied' + activity_log.exception_message = 'Deletor did not already exist' else: # Delete User user = find_actor_or_create(ap_id, create_if_not_found=False) From af3bc4f0dd143bc1de272f497fa76599ec9a3cf3 Mon Sep 17 00:00:00 2001 From: freamon Date: Mon, 21 Oct 2024 22:17:51 +0000 Subject: [PATCH 3/5] post-reply soft-deletion: activitypub restorations --- app/activitypub/routes.py | 24 ++++++++++++++++++++-- app/activitypub/util.py | 42 +++++++++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/app/activitypub/routes.py b/app/activitypub/routes.py index 2cbb6e06..de0de540 100644 --- a/app/activitypub/routes.py +++ b/app/activitypub/routes.py @@ -853,8 +853,7 @@ def process_inbox_request(request_json, activitypublog_id, ip_address): post = undo_vote(activity_log, comment, post, target_ap_id, user) elif request_json['object']['object']['type'] == 'Delete': if 'object' in request_json and 'object' in request_json['object']: - restore_post_or_comment(request_json['object']['object']) - activity_log.result = 'success' + restore_post_or_comment(request_json['object']['object'], activity_log.id) elif request_json['object']['object']['type'] == 'Block': activity_log.activity_type = 'Undo User Ban' deletor_ap_id = request_json['object']['object']['actor'] @@ -1080,6 +1079,27 @@ def process_inbox_request(request_json, activitypublog_id, ip_address): if user_ap_id.startswith('https://' + current_app.config['SERVER_NAME']): unban_local_user(deletor_ap_id, user_ap_id, target) activity_log.result = 'success' + elif request_json['object']['type'] == 'Delete': # undoing a delete + activity_log.activity_type = 'Restore' + reply = PostReply.query.filter_by(ap_id=request_json['object']['object']).first() + if reply: + deletor = find_actor_or_create(request_json['object']['actor'], create_if_not_found=False) + if deletor: + if reply.author.id == deletor.id or reply.community.is_moderator(deletor) or reply.community.is_instance_admin(deletor): + reply.deleted = False + reply.deleted_by = None + if not reply.author.bot: + reply.post.reply_count += 1 + reply.author.post_reply_count += 1 + announce_activity_to_followers(reply.community, reply.author, request_json) + db.session.commit() + activity_log.result = 'success' + else: + activity_log.exception_message = 'Restore attempt denied' + else: + activity_log.exception_message = 'Restorer did not already exist' + else: + activity_log.exception_message = 'Reply not found, or object was not a reply' elif request_json['type'] == 'Delete': if isinstance(request_json['object'], str): ap_id = request_json['object'] # lemmy diff --git a/app/activitypub/util.py b/app/activitypub/util.py index 2f6cd1e6..2615cda7 100644 --- a/app/activitypub/util.py +++ b/app/activitypub/util.py @@ -1358,21 +1358,30 @@ def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id aplog.exception_message = 'Unable to resolve deletor, community, or target' -def restore_post_or_comment(object_json): +def restore_post_or_comment(object_json, aplog_id): if current_app.debug: - restore_post_or_comment_task(object_json) + restore_post_or_comment_task(object_json, aplog_id) else: - restore_post_or_comment_task.delay(object_json) + restore_post_or_comment_task.delay(object_json, aplog_id) @celery.task -def restore_post_or_comment_task(object_json): +def restore_post_or_comment_task(object_json, aplog_id): restorer = find_actor_or_create(object_json['actor']) if 'actor' in object_json else None community = find_actor_or_create(object_json['audience'], community_only=True) if 'audience' in object_json else None to_restore = find_liked_object(object_json['object']) if 'object' in object_json else None + if to_restore and not community: + community = to_restore.community + aplog = ActivityPubLog.query.get(aplog_id) + + if to_restore and not to_restore.deleted: + if aplog: + aplog.result = 'ignored' + aplog.exception_message = 'Activity about local content which is already restored' + return if restorer and community and to_restore: - if restorer.is_admin() or community.is_moderator(restorer) or community.is_instance_admin(restorer) or to_restore.author.id == restorer.id: + if to_restore.author.id == restorer.id or restorer.is_admin() or community.is_moderator(restorer) or community.is_instance_admin(restorer): if isinstance(to_restore, Post): # TODO: restore_dependencies() to_restore.deleted = False @@ -1384,7 +1393,28 @@ def restore_post_or_comment_task(object_json): add_to_modlog_activitypub('restore_post', restorer, community_id=community.id, link_text=shorten_string(to_restore.title), link=f'post/{to_restore.id}') - # TODO: if isinstance(to_restore, PostReply): + elif isinstance(to_restore, PostReply): + to_restore.deleted = False + to_restore.deleted_by = None + if not to_restore.author.bot: + to_restore.post.reply_count += 1 + to_restore.author.post_reply_count += 1 + db.session.commit() + if to_restore.author.id != restorer.id: + add_to_modlog_activitypub('restore_post_reply', restorer, community_id=community.id, + link_text=f'comment on {shorten_string(to_restore.post.title)}', + link=f'post/{to_restore.post_id}#comment_{to_restore.id}') + + if aplog: + aplog.result = 'success' + else: + if aplog: + aplog.result = 'failure' + aplog.exception_message = 'Restorer did not have permission' + else: + if aplog: + aplog.result = 'failure' + aplog.exception_message = 'Unable to resolve restorer, community, or target' def remove_data_from_banned_user(deletor_ap_id, user_ap_id, target): From 0274682adc68bfc27138216317c565dd751e4280 Mon Sep 17 00:00:00 2001 From: freamon Date: Mon, 21 Oct 2024 23:03:53 +0000 Subject: [PATCH 4/5] Bugfix: also federate post deletion by post owners #341 --- app/post/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/post/routes.py b/app/post/routes.py index a203ac59..47975a81 100644 --- a/app/post/routes.py +++ b/app/post/routes.py @@ -1054,7 +1054,7 @@ def post_delete_post(community: Community, post: Post, user_id: int, federate_al # Federation if not community.local_only: # local_only communities do not federate # if this is a remote community and we are a mod of that community - if not post.community.is_local() and user.is_local() and (community.is_moderator(user) or community.is_owner(user)): + if not post.community.is_local() and user.is_local() and (post.user_id == user.id or community.is_moderator(user) or community.is_owner(user)): post_request(post.community.ap_inbox_url, delete_json, user.private_key, user.public_url() + '#main-key') elif post.community.is_local(): # if this is a local community - Announce it to followers on remote instances announce = { From 258dc1b9d761d4af9133497b806327c19c2a0f8b Mon Sep 17 00:00:00 2001 From: freamon Date: Tue, 22 Oct 2024 07:54:35 +0000 Subject: [PATCH 5/5] Bugfix: restore content subscription to own post --- app/community/routes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/community/routes.py b/app/community/routes.py index 4e8e88d4..f47f0daf 100644 --- a/app/community/routes.py +++ b/app/community/routes.py @@ -29,7 +29,7 @@ from app.community.util import search_for_community, actor_to_community, \ allowed_extensions, end_poll_date from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE, \ SUBSCRIPTION_PENDING, SUBSCRIPTION_MODERATOR, REPORT_STATE_NEW, REPORT_STATE_ESCALATED, REPORT_STATE_RESOLVED, \ - REPORT_STATE_DISCARDED, POST_TYPE_VIDEO, NOTIF_COMMUNITY, POST_TYPE_POLL, MICROBLOG_APPS + REPORT_STATE_DISCARDED, POST_TYPE_VIDEO, NOTIF_COMMUNITY, NOTIF_POST, POST_TYPE_POLL, MICROBLOG_APPS from app.inoculation import inoculation from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, \ File, PostVote, utcnow, Report, Notification, InstanceBlock, ActivityPubLog, Topic, Conversation, PostReply, \ @@ -703,6 +703,9 @@ def add_post(actor, type): # todo: add try..except post = Post.new(current_user, community, request_json) + if form.notify_author.data: + new_notification = NotificationSubscription(name=post.title, user_id=current_user.id, entity_id=post.id, type=NOTIF_POST) + db.session.add(new_notification) current_user.language_id = form.language_id.data g.site.last_active = utcnow() post.ap_id = f"https://{current_app.config['SERVER_NAME']}/post/{post.id}"