From 2cbaefd844a3f2a8c63fe0f1b733c223752d102c Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Fri, 22 Mar 2024 10:50:17 +1300 Subject: [PATCH 1/7] block shitpost flood --- app/activitypub/util.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/activitypub/util.py b/app/activitypub/util.py index b60e7d8c..85a92e53 100644 --- a/app/activitypub/util.py +++ b/app/activitypub/util.py @@ -1177,6 +1177,9 @@ def create_post_reply(activity_log: ActivityPubLog, community: Community, in_rep post_reply.body_html = allowlist_html(request_json['object']['content']) post_reply.body = html_to_markdown(post_reply.body_html) if post_id is not None: + # block shitpost flood + if post_reply.body and "SNEED'S" in post_reply.body: + return None post = Post.query.get(post_id) if post.comments_enabled: anchor = None From f7074d2edde05cb0baaaeb1f038f79a700c0b63b Mon Sep 17 00:00:00 2001 From: freamon Date: Thu, 21 Mar 2024 23:21:28 +0000 Subject: [PATCH 2/7] Add/Remove moderators (incoming AP) --- app/activitypub/routes.py | 44 ++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/app/activitypub/routes.py b/app/activitypub/routes.py index 799fb21e..922c2385 100644 --- a/app/activitypub/routes.py +++ b/app/activitypub/routes.py @@ -666,24 +666,48 @@ def process_inbox_request(request_json, activitypublog_id, ip_address): target_ap_id = request_json['object']['object']['object'] # object object object! post = undo_vote(activity_log, comment, post, target_ap_id, user) activity_log.result = 'success' - elif request_json['object']['type'] == 'Add': + elif request_json['object']['type'] == 'Add' and 'target' in request_json['object']: activity_log.activity_type = request_json['object']['type'] - featured_url = Community.query.filter(Community.ap_public_url == request_json['actor']).first().ap_featured_url - if featured_url: - if 'target' in request_json['object'] and featured_url == request_json['object']['target']: - post = Post.query.filter(Post.ap_id == request_json['object']['object']).first() + target = request_json['object']['target'] + community = Community.query.filter_by(ap_public_url=request_json['actor']).first() + if community: + featured_url = community.ap_featured_url + moderators_url = community.ap_moderators_url + if target == featured_url: + post = Post.query.filter_by(ap_id=request_json['object']['object']).first() if post: post.sticky = True activity_log.result = 'success' - elif request_json['object']['type'] == 'Remove': + if target == moderators_url: + user = find_actor_or_create(request_json['object']['object']) + if user: + existing_membership = CommunityMember.query.filter_by(community_id=community.id, user_id=user.id).first() + if existing_membership: + existing_membership.is_moderator = True + else: + new_membership = CommunityMember(community_id=community.id, user_id=user.id, is_moderator=True) + db.session.add(new_membership) + db.session.commit() + activity_log.result = 'success' + elif request_json['object']['type'] == 'Remove' and 'target' in request_json['object']: activity_log.activity_type = request_json['object']['type'] - featured_url = Community.query.filter(Community.ap_public_url == request_json['actor']).first().ap_featured_url - if featured_url: - if 'target' in request_json['object'] and featured_url == request_json['object']['target']: - post = Post.query.filter(Post.ap_id == request_json['object']['object']).first() + target = request_json['object']['target'] + community = Community.query.filter_by(ap_public_url=request_json['actor']).first() + if community: + featured_url = community.ap_featured_url + moderators_url = community.ap_moderators_url + if target == featured_url: + post = Post.query.filter_by(ap_id=request_json['object']['object']).first() if post: post.sticky = False activity_log.result = 'success' + if target == moderators_url: + user = find_actor_or_create(request_json['object']['object'], create_if_not_found=False) + if user: + existing_membership = CommunityMember.query.filter_by(community_id=community.id, user_id=user.id).first() + if existing_membership: + existing_membership.is_moderator = False + activity_log.result = 'success' else: activity_log.exception_message = 'Invalid type for Announce' From 081108a7c6aea74c05d1ce232c9b2bf73aa1bcf9 Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Fri, 22 Mar 2024 12:22:19 +1300 Subject: [PATCH 3/7] block future shitpost floods --- app/activitypub/util.py | 20 +++++++++--- app/admin/forms.py | 1 + app/admin/routes.py | 12 +++++-- app/community/util.py | 15 ++++++++- app/models.py | 3 +- app/post/routes.py | 6 +++- app/utils.py | 9 ++++++ .../versions/2b028a70bd7a_blocked_phrases.py | 32 +++++++++++++++++++ 8 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 migrations/versions/2b028a70bd7a_blocked_phrases.py diff --git a/app/activitypub/util.py b/app/activitypub/util.py index 85a92e53..239dedcf 100644 --- a/app/activitypub/util.py +++ b/app/activitypub/util.py @@ -24,7 +24,8 @@ import pytesseract from app.utils import get_request, allowlist_html, html_to_markdown, get_setting, ap_datetime, markdown_to_html, \ is_image_url, domain_from_url, gibberish, ensure_directory_exists, markdown_to_text, head_request, post_ranking, \ - shorten_string, reply_already_exists, reply_is_just_link_to_gif_reaction, confidence, remove_tracking_from_link + shorten_string, reply_already_exists, reply_is_just_link_to_gif_reaction, confidence, remove_tracking_from_link, \ + blocked_phrases def public_key(): @@ -1177,9 +1178,11 @@ def create_post_reply(activity_log: ActivityPubLog, community: Community, in_rep post_reply.body_html = allowlist_html(request_json['object']['content']) post_reply.body = html_to_markdown(post_reply.body_html) if post_id is not None: - # block shitpost flood - if post_reply.body and "SNEED'S" in post_reply.body: - return None + # Discard post_reply if it contains certain phrases. Good for stopping spam floods. + if post_reply.body: + for blocked_phrase in blocked_phrases(): + if blocked_phrase in post_reply.body: + return None post = Post.query.get(post_id) if post.comments_enabled: anchor = None @@ -1273,6 +1276,15 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json elif 'content' in request_json['object'] and request_json['object']['content'] is not None: # Kbin post.body_html = allowlist_html(request_json['object']['content']) post.body = html_to_markdown(post.body_html) + # Discard post if it contains certain phrases. Good for stopping spam floods. + blocked_phrases_list = blocked_phrases() + for blocked_phrase in blocked_phrases_list: + if blocked_phrase in post.title: + return None + if post.body: + for blocked_phrase in blocked_phrases_list: + if blocked_phrase in post.body: + return None if 'attachment' in request_json['object'] and len(request_json['object']['attachment']) > 0 and \ 'type' in request_json['object']['attachment'][0]: if request_json['object']['attachment'][0]['type'] == 'Link': diff --git a/app/admin/forms.py b/app/admin/forms.py index b0b8ff2c..433f224c 100644 --- a/app/admin/forms.py +++ b/app/admin/forms.py @@ -41,6 +41,7 @@ class FederationForm(FlaskForm): allowlist = TextAreaField(_l('Allow federation with these instances')) use_blocklist = BooleanField(_l('Blocklist instead of allowlist')) blocklist = TextAreaField(_l('Deny federation with these instances')) + blocked_phrases = TextAreaField(_l('Discard all posts and comments with these phrases (one per line)')) submit = SubmitField(_l('Save')) diff --git a/app/admin/routes.py b/app/admin/routes.py index a53d987d..a8068bb4 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -6,10 +6,10 @@ from flask_login import login_required, current_user from flask_babel import _ from sqlalchemy import text, desc -from app import db, celery +from app import db, celery, cache from app.activitypub.routes import process_inbox_request, process_delete_request from app.activitypub.signature import post_request -from app.activitypub.util import default_context +from app.activitypub.util import default_context, instance_allowed, instance_blocked from app.admin.forms import FederationForm, SiteMiscForm, SiteProfileForm, EditCommunityForm, EditUserForm, \ EditTopicForm, SendNewsletterForm, AddUserForm from app.admin.util import unsubscribe_from_everything_then_delete, unsubscribe_from_community, send_newsletter, \ @@ -18,7 +18,7 @@ from app.community.util import save_icon_file, save_banner_file from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow, Site, Community, CommunityMember, \ User, Instance, File, Report, Topic, UserRegistration, Role, Post from app.utils import render_template, permission_required, set_setting, get_setting, gibberish, markdown_to_html, \ - moderating_communities, joined_communities, finalize_user_setup, theme_list + moderating_communities, joined_communities, finalize_user_setup, theme_list, blocked_phrases from app.admin import bp @@ -123,13 +123,18 @@ def admin_federation(): for allow in form.allowlist.data.split('\n'): if allow.strip(): db.session.add(AllowedInstances(domain=allow.strip())) + cache.delete_memoized(instance_allowed, allow.strip()) if form.use_blocklist.data: set_setting('use_allowlist', False) db.session.execute(text('DELETE FROM banned_instances')) for banned in form.blocklist.data.split('\n'): if banned.strip(): db.session.add(BannedInstances(domain=banned.strip())) + cache.delete_memoized(instance_blocked, banned.strip()) + site.blocked_phrases = form.blocked_phrases.data + cache.delete_memoized(blocked_phrases) db.session.commit() + flash(_('Admin settings saved')) elif request.method == 'GET': @@ -139,6 +144,7 @@ def admin_federation(): form.blocklist.data = '\n'.join([instance.domain for instance in instances]) instances = AllowedInstances.query.all() form.allowlist.data = '\n'.join([instance.domain for instance in instances]) + form.blocked_phrases.data = site.blocked_phrases return render_template('admin/federation.html', title=_('Federation settings'), form=form, moderating_communities=moderating_communities(current_user.get_id()), diff --git a/app/community/util.py b/app/community/util.py index aedb3615..758f0bd3 100644 --- a/app/community/util.py +++ b/app/community/util.py @@ -16,7 +16,7 @@ from app.models import Community, File, BannedInstances, PostReply, PostVote, Po Instance, Notification, User, ActivityPubLog from app.utils import get_request, gibberish, markdown_to_html, domain_from_url, allowlist_html, \ html_to_markdown, is_image_url, ensure_directory_exists, inbox_domain, post_ranking, shorten_string, parse_page, \ - remove_tracking_from_link, ap_datetime, instance_banned + remove_tracking_from_link, ap_datetime, instance_banned, blocked_phrases from sqlalchemy import func, desc import os @@ -299,6 +299,19 @@ def save_post(form, post: Post): if current_user.reputation < -100: post.score = -1 post.ranking = post_ranking(post.score, utcnow()) + + # Filter by phrase + blocked_phrases_list = blocked_phrases() + for blocked_phrase in blocked_phrases_list: + if blocked_phrase in post.title: + abort(401) + return + if post.body: + for blocked_phrase in blocked_phrases_list: + if blocked_phrase in post.body: + abort(401) + return + db.session.add(post) g.site.last_active = utcnow() diff --git a/app/models.py b/app/models.py index 9b2d7a10..5c0c4eb3 100644 --- a/app/models.py +++ b/app/models.py @@ -1170,7 +1170,8 @@ class Site(db.Model): allow_or_block_list = db.Column(db.Integer, default=2) # 1 = allow list, 2 = block list allowlist = db.Column(db.Text, default='') blocklist = db.Column(db.Text, default='') - auto_decline_referrers = db.Column(db.Text, default='rdrama.net') + blocked_phrases = db.Column(db.Text, default='') # discard incoming content with these phrases + auto_decline_referrers = db.Column(db.Text, default='rdrama.net') # automatically decline registration requests if the referrer is one of these created_at = db.Column(db.DateTime, default=utcnow) updated = db.Column(db.DateTime, default=utcnow) last_active = db.Column(db.DateTime, default=utcnow) diff --git a/app/post/routes.py b/app/post/routes.py index f506c68f..14990cbe 100644 --- a/app/post/routes.py +++ b/app/post/routes.py @@ -24,7 +24,7 @@ from app.utils import get_setting, render_template, allowlist_html, markdown_to_ shorten_string, markdown_to_text, gibberish, ap_datetime, return_304, \ request_etag_matches, ip_address, user_ip_banned, instance_banned, can_downvote, can_upvote, post_ranking, \ reply_already_exists, reply_is_just_link_to_gif_reaction, confidence, moderating_communities, joined_communities, \ - blocked_instances, blocked_domains, community_moderators + blocked_instances, blocked_domains, community_moderators, blocked_phrases def show_post(post_id: int): @@ -466,6 +466,10 @@ def add_reply(post_id: int, comment_id: int): body_html=markdown_to_html(form.body.data), body_html_safe=True, from_bot=current_user.bot, nsfw=post.nsfw, nsfl=post.nsfl, notify_author=form.notify_author.data, instance_id=1) + if reply.body: + for blocked_phrase in blocked_phrases(): + if blocked_phrase in reply.body: + abort(401) db.session.add(reply) if in_reply_to.notify_author and current_user.id != in_reply_to.user_id and in_reply_to.author.ap_id is None: # todo: check if replier is blocked notification = Notification(title=shorten_string(_('Reply from %(name)s on %(post_title)s', diff --git a/app/utils.py b/app/utils.py index 84c462e3..aef7ac30 100644 --- a/app/utils.py +++ b/app/utils.py @@ -330,6 +330,15 @@ def blocked_instances(user_id) -> List[int]: return [block.instance_id for block in blocks] +@cache.memoize(timeout=86400) +def blocked_phrases() -> List[str]: + site = Site.query.get(1) + if site.blocked_phrases: + return [phrase for phrase in site.blocked_phrases.split('\n') if phrase != ''] + else: + return [] + + def retrieve_block_list(): try: response = requests.get('https://raw.githubusercontent.com/rimu/no-qanon/master/domains.txt', timeout=1) diff --git a/migrations/versions/2b028a70bd7a_blocked_phrases.py b/migrations/versions/2b028a70bd7a_blocked_phrases.py new file mode 100644 index 00000000..0f987347 --- /dev/null +++ b/migrations/versions/2b028a70bd7a_blocked_phrases.py @@ -0,0 +1,32 @@ +"""blocked phrases + +Revision ID: 2b028a70bd7a +Revises: 12d60b9d5417 +Create Date: 2024-03-22 11:50:15.405786 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '2b028a70bd7a' +down_revision = '12d60b9d5417' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('site', schema=None) as batch_op: + batch_op.add_column(sa.Column('blocked_phrases', sa.Text(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('site', schema=None) as batch_op: + batch_op.drop_column('blocked_phrases') + + # ### end Alembic commands ### From 4804c4c4b2456389d0399fd6852aeaf093309af8 Mon Sep 17 00:00:00 2001 From: freamon Date: Thu, 21 Mar 2024 23:26:03 +0000 Subject: [PATCH 4/7] Avoid returning 'null' for manuallyApprovesFollowers --- app/activitypub/routes.py | 2 +- app/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/activitypub/routes.py b/app/activitypub/routes.py index 922c2385..6bd0a8cf 100644 --- a/app/activitypub/routes.py +++ b/app/activitypub/routes.py @@ -203,7 +203,7 @@ def user_profile(actor): "outbox": f"https://{server}/u/{actor}/outbox", "discoverable": user.searchable, "indexable": user.indexable, - "manuallyApprovesFollowers": user.ap_manually_approves_followers, + "manuallyApprovesFollowers": False if not user.ap_manually_approves_followers else user.ap_manually_approves_followers, "publicKey": { "id": f"https://{server}/u/{actor}#main-key", "owner": f"https://{server}/u/{actor}", diff --git a/app/models.py b/app/models.py index 9b2d7a10..93e0c1fe 100644 --- a/app/models.py +++ b/app/models.py @@ -488,7 +488,7 @@ class User(UserMixin, db.Model): ap_fetched_at = db.Column(db.DateTime) ap_followers_url = db.Column(db.String(255)) ap_preferred_username = db.Column(db.String(255)) - ap_manually_approves_followers = db.Column(db.Boolean) + ap_manually_approves_followers = db.Column(db.Boolean, default=False) ap_deleted_at = db.Column(db.DateTime) ap_inbox_url = db.Column(db.String(255)) ap_domain = db.Column(db.String(255)) From 75c3c0b49b8cc03b4600290c900b3a4c6e80376b Mon Sep 17 00:00:00 2001 From: freamon Date: Fri, 22 Mar 2024 01:23:26 +0000 Subject: [PATCH 5/7] mod_list in side-panel for show_post, add_reply, etc --- app/post/routes.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/app/post/routes.py b/app/post/routes.py index f506c68f..df58cf9b 100644 --- a/app/post/routes.py +++ b/app/post/routes.py @@ -18,7 +18,7 @@ from app.post.util import post_replies, get_comment_branch, post_reply_count from app.constants import SUBSCRIPTION_MEMBER, POST_TYPE_LINK, POST_TYPE_IMAGE from app.models import Post, PostReply, \ PostReplyVote, PostVote, Notification, utcnow, UserBlock, DomainBlock, InstanceBlock, Report, Site, Community, \ - Topic + Topic, User from app.post import bp from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \ shorten_string, markdown_to_text, gibberish, ap_datetime, return_304, \ @@ -47,6 +47,12 @@ def show_post(post_id: int): mods = community_moderators(community.id) is_moderator = current_user.is_authenticated and any(mod.user_id == current_user.id for mod in mods) + if community.private_mods: + mod_list = [] + else: + mod_user_ids = [mod.user_id for mod in mods] + mod_list = User.query.filter(User.id.in_(mod_user_ids)).all() + # handle top-level comments/replies form = NewReplyForm() if current_user.is_authenticated and current_user.verified and form.validate_on_submit(): @@ -213,7 +219,7 @@ def show_post(post_id: int): breadcrumbs.append(breadcrumb) response = render_template('post/post.html', title=post.title, post=post, is_moderator=is_moderator, community=post.community, - breadcrumbs=breadcrumbs, related_communities=related_communities, + breadcrumbs=breadcrumbs, related_communities=related_communities, mods=mod_list, canonical=post.ap_id, form=form, replies=replies, THREAD_CUTOFF_DEPTH=constants.THREAD_CUTOFF_DEPTH, description=description, og_image=og_image, POST_TYPE_IMAGE=constants.POST_TYPE_IMAGE, POST_TYPE_LINK=constants.POST_TYPE_LINK, POST_TYPE_ARTICLE=constants.POST_TYPE_ARTICLE, @@ -409,9 +415,14 @@ def continue_discussion(post_id, comment_id): abort(404) mods = post.community.moderators() is_moderator = current_user.is_authenticated and any(mod.user_id == current_user.id for mod in mods) + if post.community.private_mods: + mod_list = [] + else: + mod_user_ids = [mod.user_id for mod in mods] + mod_list = User.query.filter(User.id.in_(mod_user_ids)).all() replies = get_comment_branch(post.id, comment.id, 'top') - response = render_template('post/continue_discussion.html', title=_('Discussing %(title)s', title=post.title), post=post, + response = render_template('post/continue_discussion.html', title=_('Discussing %(title)s', title=post.title), post=post, mods=mod_list, is_moderator=is_moderator, comment=comment, replies=replies, markdown_editor=current_user.is_authenticated and current_user.markdown_editor, moderating_communities=moderating_communities(current_user.get_id()), joined_communities=joined_communities(current_user.get_id()), community=post.community, @@ -438,6 +449,11 @@ def add_reply(post_id: int, comment_id: int): in_reply_to = PostReply.query.get_or_404(comment_id) mods = post.community.moderators() is_moderator = current_user.is_authenticated and any(mod.user_id == current_user.id for mod in mods) + if post.community.private_mods: + mod_list = [] + else: + mod_user_ids = [mod.user_id for mod in mods] + mod_list = User.query.filter(User.id.in_(mod_user_ids)).all() if in_reply_to.author.has_blocked_user(current_user.id): flash(_('You cannot reply to %(name)s', name=in_reply_to.author.display_name())) @@ -578,7 +594,7 @@ def add_reply(post_id: int, comment_id: int): form.notify_author.data = True return render_template('post/add_reply.html', title=_('Discussing %(title)s', title=post.title), post=post, is_moderator=is_moderator, form=form, comment=in_reply_to, markdown_editor=current_user.is_authenticated and current_user.markdown_editor, - moderating_communities=moderating_communities(current_user.get_id()), + moderating_communities=moderating_communities(current_user.get_id()), mods=mod_list, joined_communities = joined_communities(current_user.id), inoculation=inoculation[randint(0, len(inoculation) - 1)]) @@ -607,6 +623,14 @@ def post_edit(post_id: int): post = Post.query.get_or_404(post_id) form = CreatePostForm() del form.communities + + mods = post.community.moderators() + if post.community.private_mods: + mod_list = [] + else: + mod_user_ids = [mod.user_id for mod in mods] + mod_list = User.query.filter(User.id.in_(mod_user_ids)).all() + if post.user_id == current_user.id or post.community.is_moderator() or current_user.is_admin(): if g.site.enable_nsfl is False: form.nsfl.render_kw = {'disabled': True} @@ -727,7 +751,7 @@ def post_edit(post_id: int): if not (post.community.is_moderator() or post.community.is_owner() or current_user.is_admin()): form.sticky.render_kw = {'disabled': True} return render_template('post/post_edit.html', title=_('Edit post'), form=form, post=post, - markdown_editor=current_user.markdown_editor, + markdown_editor=current_user.markdown_editor, mods=mod_list, moderating_communities=moderating_communities(current_user.get_id()), joined_communities=joined_communities(current_user.get_id()), inoculation=inoculation[randint(0, len(inoculation) - 1)] From ef43e78ae5b64d56e00e05659ea77d5020794bb3 Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Fri, 22 Mar 2024 14:35:51 +1300 Subject: [PATCH 6/7] automatically block new user registrations based on referrer --- app/admin/forms.py | 1 + app/admin/routes.py | 5 ++++- app/auth/routes.py | 7 ++++++- app/models.py | 2 +- app/utils.py | 8 ++++++++ 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/admin/forms.py b/app/admin/forms.py index 433f224c..c47280f9 100644 --- a/app/admin/forms.py +++ b/app/admin/forms.py @@ -31,6 +31,7 @@ class SiteMiscForm(FlaskForm): types = [('Open', _l('Open')), ('RequireApplication', _l('Require application')), ('Closed', _l('Closed'))] registration_mode = SelectField(_l('Registration mode'), choices=types, default=1, coerce=str) application_question = TextAreaField(_l('Question to ask people applying for an account')) + auto_decline_referrers = TextAreaField(_l('Block registrations from these referrers (one per line)')) log_activitypub_json = BooleanField(_l('Log ActivityPub JSON for debugging')) default_theme = SelectField(_l('Default theme'), coerce=str) submit = SubmitField(_l('Save')) diff --git a/app/admin/routes.py b/app/admin/routes.py index a8068bb4..776d1169 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -18,7 +18,7 @@ from app.community.util import save_icon_file, save_banner_file from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow, Site, Community, CommunityMember, \ User, Instance, File, Report, Topic, UserRegistration, Role, Post from app.utils import render_template, permission_required, set_setting, get_setting, gibberish, markdown_to_html, \ - moderating_communities, joined_communities, finalize_user_setup, theme_list, blocked_phrases + moderating_communities, joined_communities, finalize_user_setup, theme_list, blocked_phrases, blocked_referrers from app.admin import bp @@ -80,12 +80,14 @@ def admin_misc(): site.reports_email_admins = form.reports_email_admins.data site.registration_mode = form.registration_mode.data site.application_question = form.application_question.data + site.auto_decline_referrers = form.auto_decline_referrers.data site.log_activitypub_json = form.log_activitypub_json.data site.updated = utcnow() site.default_theme = form.default_theme.data if site.id is None: db.session.add(site) db.session.commit() + cache.delete_memoized(blocked_referrers) flash('Settings saved.') elif request.method == 'GET': form.enable_downvotes.data = site.enable_downvotes @@ -97,6 +99,7 @@ def admin_misc(): form.reports_email_admins.data = site.reports_email_admins form.registration_mode.data = site.registration_mode form.application_question.data = site.application_question + form.auto_decline_referrers.data = site.auto_decline_referrers form.log_activitypub_json.data = site.log_activitypub_json form.default_theme.data = site.default_theme if site.default_theme is not None else '' return render_template('admin/misc.html', title=_('Misc settings'), form=form, diff --git a/app/auth/routes.py b/app/auth/routes.py index 14f232be..4117c02c 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -12,7 +12,7 @@ from app.auth.util import random_token, normalize_utf from app.email import send_verification_email, send_password_reset_email from app.models import User, utcnow, IpBan, UserRegistration, Notification, Site from app.utils import render_template, ip_address, user_ip_banned, user_cookie_banned, banned_ip_addresses, \ - finalize_user_setup + finalize_user_setup, blocked_referrers @bp.route('/login', methods=['GET', 'POST']) @@ -98,6 +98,11 @@ def register(): if form.user_name.data in disallowed_usernames: flash(_('Sorry, you cannot use that user name'), 'error') else: + for referrer in blocked_referrers(): + if referrer in session.get('Referer'): + resp = make_response(redirect(url_for('auth.please_wait'))) + resp.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30)) + return resp verification_token = random_token(16) form.user_name.data = form.user_name.data.strip() before_normalize = form.user_name.data diff --git a/app/models.py b/app/models.py index 5c0c4eb3..0ae48c51 100644 --- a/app/models.py +++ b/app/models.py @@ -1171,7 +1171,7 @@ class Site(db.Model): allowlist = db.Column(db.Text, default='') blocklist = db.Column(db.Text, default='') blocked_phrases = db.Column(db.Text, default='') # discard incoming content with these phrases - auto_decline_referrers = db.Column(db.Text, default='rdrama.net') # automatically decline registration requests if the referrer is one of these + auto_decline_referrers = db.Column(db.Text, default='rdrama.net\nahrefs.com') # automatically decline registration requests if the referrer is one of these created_at = db.Column(db.DateTime, default=utcnow) updated = db.Column(db.DateTime, default=utcnow) last_active = db.Column(db.DateTime, default=utcnow) diff --git a/app/utils.py b/app/utils.py index aef7ac30..5c6d2fcd 100644 --- a/app/utils.py +++ b/app/utils.py @@ -339,6 +339,14 @@ def blocked_phrases() -> List[str]: return [] +@cache.memoize(timeout=86400) +def blocked_referrers() -> List[str]: + site = Site.query.get(1) + if site.auto_decline_referrers: + return [referrer for referrer in site.auto_decline_referrers.split('\n') if referrer != ''] + else: + return [] + def retrieve_block_list(): try: response = requests.get('https://raw.githubusercontent.com/rimu/no-qanon/master/domains.txt', timeout=1) From ab60bc753b3bab4985e42e3b454eccb6c1246bf8 Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Fri, 22 Mar 2024 20:49:35 +1300 Subject: [PATCH 7/7] correct Accept header --- app/community/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/community/util.py b/app/community/util.py index 758f0bd3..44642fb0 100644 --- a/app/community/util.py +++ b/app/community/util.py @@ -122,7 +122,7 @@ def retrieve_mods_and_backfill(community_id: int): c.last_active = Post.query.filter(Post.community_id == community_id).order_by(desc(Post.posted_at)).first().posted_at db.session.commit() if community.ap_featured_url: - featured_request = get_request(community.ap_featured_url, headers={'Accept': 'application/activityjson'}) + featured_request = get_request(community.ap_featured_url, headers={'Accept': 'application/activity+json'}) if featured_request.status_code == 200: featured_data = featured_request.json() featured_request.close()