add new `reject unknown` policy option to prevent spam & harassment from large/undermoderated servers

staging
multiple creatures 2019-08-08 09:59:14 -05:00
parent d019e55b7b
commit 4dfc40324b
31 changed files with 222 additions and 37 deletions

View File

@ -2,7 +2,7 @@
module Admin
class AccountsController < BaseController
before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :allow_public, :allow_nonsensitive, :unsilence, :unsuspend, :memorialize, :approve, :reject]
before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :mark_known, :mark_unknown, :allow_public, :allow_nonsensitive, :unsilence, :unsuspend, :memorialize, :approve, :reject]
before_action :require_remote_account!, only: [:redownload]
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
@ -45,6 +45,20 @@ module Admin
redirect_to admin_accounts_path(pending: '1')
end
def mark_unknown
authorize @account, :mark_unknown?
@account.mark_unknown!
log_action :mark_unknown, @account
redirect_to admin_account_path(@account.id)
end
def mark_known
authorize @account, :mark_known?
@account.mark_known!
log_action :mark_known, @account
redirect_to admin_account_path(@account.id)
end
def force_sensitive
authorize @account, :force_sensitive?
@account.force_sensitive!

View File

@ -50,7 +50,7 @@ module Admin
@domain_block.update(resource_params.except(:domain, :undo))
changed = @domain_block.changed
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id) if (changed & %w(severity force_sensitive reject_media)).any?
DomainBlockWorker.perform_async(@domain_block.id) if (changed & %w(severity force_sensitive reject_media reject_unknown)).any?
log_action :update, @domain_block
flash[:notice] = I18n.t('admin.domain_blocks.updated_msg')
else
@ -66,7 +66,7 @@ module Admin
end
def resource_params
params.require(:domain_block).permit(:domain, :severity, :force_sensitive, :reject_media, :reject_reports, :reason, :undo)
params.require(:domain_block).permit(:domain, :severity, :force_sensitive, :reject_media, :reject_reports, :reject_unknown, :reason, :undo)
end
end
end

View File

@ -5,7 +5,7 @@ module LogHelper
case action
when :create
if target.is_a? DomainBlock
LogWorker.perform_async("\xf0\x9f\x9a\xab <#{source}> applied a #{target.severity}#{target.force_sensitive? ? " and force sensitive media" : ''}#{target.reject_media? ? " and reject media" : ''} policy on https://#{target.domain}\u200b.\n\n#{target.reason? ? "Comment: #{target.reason}" : ''}")
LogWorker.perform_async("\xf0\x9f\x9a\xab <#{source}> applied a #{target.severity}#{target.force_sensitive? ? " and force sensitive media" : ''}#{target.reject_media? ? " and reject media" : ''}#{target.reject_unknown? ? " and reject unknown accounts" : ''} policy on https://#{target.domain}\u200b.\n\n#{target.reason? ? "Comment: #{target.reason}" : ''}")
elsif target.is_a? EmailDomainBlock
LogWorker.perform_async("\u26d4 <#{source}> added a registration block on email domain '#{target.domain}'.")
elsif target.is_a? CustomEmoji
@ -26,7 +26,7 @@ module LogHelper
when :update
if target.is_a? DomainBlock
LogWorker.perform_async("\xf0\x9f\x9a\xab <#{source}> changed the policy on https://#{target.domain} to #{target.severity}#{target.force_sensitive? ? " and force sensitive media" : ''}#{target.reject_media? ? " and reject media" : ''}.\n\n#{target.reason? ? "Comment: #{target.reason}" : ''}")
LogWorker.perform_async("\xf0\x9f\x9a\xab <#{source}> changed the policy on https://#{target.domain} to #{target.severity}#{target.force_sensitive? ? " and force sensitive media" : ''}#{target.reject_media? ? " and reject media" : ''}#{target.reject_unknown? ? " and reject unknown accounts" : ''}.\n\n#{target.reason? ? "Comment: #{target.reason}" : ''}")
elsif target.is_a? Status
LogWorker.perform_async("\xf0\x9f\x91\x81\xef\xb8\x8f <#{source}> changed visibility flags of post #{TagManager.instance.url_for(target)}\u200b.")
elsif target.is_a? CustomEmoji
@ -46,17 +46,21 @@ module LogHelper
LogWorker.perform_async("\u26d4 <#{source}> disabled the '#{target.shortcode}' emoji.")
end
when :mark_unknown
LogWorker.perform_async("\u2753 <#{source}> marked <#{target.acct}> as an unknown account.\n\n#{reason ? "Comment: #{reason}" : ''}")
when :force_sensitive
LogWorker.perform_async("\xf0\x9f\x94\x9e <#{source}> forced the media of <#{target.acct}> to be marked sensitive.\n\n#{reason ? "Comment: #{reason}" : ''}")
when :force_unlisted
LogWorker.perform_async("\xf0\x9f\x94\x89 <#{source}> forced the posts of <#{target.acct}> to be unlisted.\n\n#{reason ? "Comment: #{reason}" : ''}")
when :silence
LogWorker.perform_async("\xf0\x9f\x94\x87 <#{source}> silenced <#{target.acct}>'.\n\n#{reason ? "Comment: #{reason}" : ''}")
LogWorker.perform_async("\xf0\x9f\x94\x87 <#{source}> silenced <#{target.acct}>.\n\n#{reason ? "Comment: #{reason}" : ''}")
when :suspend
LogWorker.perform_async("\u26d4 <#{source}> suspended <#{target.acct}>.\n\n#{reason ? "Comment: #{reason}" : ''}")
when :mark_known
LogWorker.perform_async("\u2705 <#{source}> marked <#{target.acct}> as a known account.\n\n#{reason ? "Comment: #{reason}" : ''}")
when :allow_nonsensitive
LogWorker.perform_async("\xf0\x9f\x86\x97 <#{source}> allowed <#{target.acct}> to post media without a sensitive flag.")
LogWorker.perform_async("\xf0\x9f\x86\x97 <#{source}> allowed <#{target.acct}> to post media without a sensitive flag.\n\n#{reason ? "Comment: #{reason}" : ''}")
when :allow_public
LogWorker.perform_async("\xf0\x9f\x86\x8a <#{source}> allowed <#{target.acct}> to post with public visibility.")
when :unsilence

View File

@ -1,7 +1,7 @@
module ModerationHelper
include LogHelper
POLICIES = %w(silence unsilence suspend unsuspend force_unlisted allow_public force_sensitive allow_nonsensitive reset)
POLICIES = %w(silence unsilence suspend unsuspend force_unlisted mark_known mark_unknown reject_unknown allow_public force_sensitive allow_nonsensitive reset)
EXCLUDED_DOMAINS = %w(tailma.ws monsterpit.net monsterpit.cloud monsterpit.gallery monsterpit.blog)
def janitor_account
@ -30,6 +30,10 @@ module ModerationHelper
end
case policy
when 'mark_unknown', 'reject_unknown'
acct.mark_unknown!
when 'mark_known'
acct.mark_known!
when 'silence'
acct.silence!
when 'unsilence'
@ -52,6 +56,7 @@ module ModerationHelper
acct.unsilence!
acct.allow_public!
acct.allow_nonsensitive!
acct.mark_known!
end
acct.save
@ -77,7 +82,7 @@ module ModerationHelper
true
end
def domain_policy(domain, policy, reason = nil, force_sensitive = false, reject_media = false, reject_reports = false)
def domain_policy(domain, policy, reason = nil, force_sensitive: false, reject_unknown: false, reject_media: false, reject_reports: false)
return if policy.blank?
policy = policy.to_s
return false unless policy.in?(POLICIES)
@ -87,10 +92,9 @@ module ModerationHelper
return false if domain.in?(EXCLUDED_DOMAINS)
if policy == 'force_sensitive'
policy = 'noop'
force_sensitive = true
end
policy = 'noop' if policy == 'force_sensitive' || policy == 'reject_unknown'
force_sensitive = true if policy == 'force_sensitive'
reject_unknown = true if policy == 'reject_unknown'
if policy.in? %w(silence suspend force_unlisted)
return false unless domain_exists?(domain)
@ -98,6 +102,7 @@ module ModerationHelper
domain_block = DomainBlock.find_or_create_by(domain: domain)
domain_block.severity = policy
domain_block.force_sensitive = force_sensitive
domain_block.reject_unknown = reject_unknown
domain_block.reject_media = reject_media
domain_block.reject_reports = reject_reports
domain_block.reason = reason.strip if reason && !reason.strip.blank?

View File

@ -137,7 +137,7 @@ class ActivityPub::Activity
redis.setex("delete_upon_arrival:#{@account.id}:#{uri}", 6.hours.seconds, uri)
end
def status_from_object
def status_from_object(announced_by: nil)
# If the status is already known, return it
status = status_from_uri(object_uri)
@ -148,19 +148,20 @@ class ActivityPub::Activity
actor_id = value_or_id(first_of_value(@object['attributedTo'])) || @account.uri
if actor_id == @account.uri
return ActivityPub::Activity.factory({ 'type' => 'Create', 'actor' => actor_id, 'object' => @object }, @account).perform
return ActivityPub::Activity.factory({ 'type' => 'Create', 'actor' => actor_id, 'object' => @object }, @account, announced_by: announced_by).perform
end
end
fetch_remote_original_status
fetch_remote_original_status(announced_by: announced_by)
end
def fetch_remote_original_status
def fetch_remote_original_status(announced_by: nil)
if object_uri.start_with?('http')
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, on_behalf_of: @account.followers.local.first)
ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, on_behalf_of: @account.followers.local.first, announced_by: announced_by)
elsif @object['url'].present?
::FetchRemoteStatusService.new.call(@object['url'])
options[:id] = true
::FetchRemoteStatusService.new.call(@object['url'], announced_by: announced_by)
end
end
@ -182,6 +183,17 @@ class ActivityPub::Activity
@options[:relayed_through_account] && Relay.find_by(inbox_url: @options[:relayed_through_account].inbox_url)&.enabled?
end
def rejecting_unknown?(account = nil)
account = @account if account.nil?
DomainBlock.where(domain: account.domain, reject_unknown: true).exists?
end
def known?(account = nil)
account = @account if account.nil?
return true if account.known?
account.passive_relationships.exists?
end
def reject_payload!
Rails.logger.info("Rejected #{@json['type']} activity #{@json['id']} from #{@account.uri}#{@options[:relayed_through_account] && "via #{@options[:relayed_through_account].uri}"}")
nil

View File

@ -5,7 +5,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
return if autoreject?
return reject_payload! if !@options[:imported] && (delete_arrived_first?(@json['id']) || !related_to_local_activity?)
original_status = status_from_object
original_status = status_from_object(announced_by: @account)
return reject_payload! if original_status.nil? || !announceable?(original_status)

View File

@ -5,6 +5,14 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
return if autoreject?
return reject_payload! if unsupported_object_type? || !@options[:imported] && (invalid_origin?(@object['id']) || Tombstone.exists?(uri: @object['id']) || !related_to_local_activity?)
unless known?
if @options[:announced_by].nil?
return reject_payload! if !@options[:requested] && rejecting_unknown?
else
@account.mark_known! if known?(@options[:announced_by])
end
end
RedisLock.acquire(lock_options) do |lock|
if lock.acquired?
return if !@options[:imported] && (delete_arrived_first?(object_uri) || poll_vote?)

View File

@ -7,7 +7,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.requested?(target_account)
if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved?
if (rejecting_unknown? && !known?) || target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved?
reject_follow_request!(target_account)
return
end

View File

@ -51,6 +51,7 @@
# gently :boolean default(FALSE), not null
# kobold :boolean default(FALSE), not null
# froze :boolean
# known :boolean default(FALSE), not null
#
class Account < ApplicationRecord
@ -213,6 +214,14 @@ class Account < ApplicationRecord
ResolveAccountService.new.call(acct)
end
def mark_unknown!
update!(known: false)
end
def mark_known!
update!(known: true)
end
def force_unlisted!
transaction do
update!(force_unlisted: true)
@ -557,6 +566,7 @@ class Account < ApplicationRecord
before_create :generate_keys
before_create :set_domain_from_inbox_url
before_create :set_known, if: :local?
before_validation :prepare_contents, if: :local?
before_validation :prepare_username, on: :create
before_destroy :clean_feed_manager
@ -579,6 +589,10 @@ class Account < ApplicationRecord
nil
end
def set_known
self.known = true
end
def generate_keys
return unless local? && !Rails.env.test?

View File

@ -13,7 +13,7 @@
#
class AccountWarning < ApplicationRecord
enum action: %i(none disable force_sensitive force_unlisted silence suspend), _suffix: :action
enum action: %i(none disable force_sensitive force_unlisted silence suspend mark_unknown), _suffix: :action
belongs_to :account, inverse_of: :account_warnings
belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings

View File

@ -12,6 +12,7 @@ class Admin::AccountAction
force_unlisted
silence
suspend
mark_unknown
).freeze
attr_accessor :target_account,
@ -66,6 +67,8 @@ class Admin::AccountAction
handle_silence!
when 'suspend'
handle_suspend!
when 'mark_unknown'
handle_mark_unknown!
end
end
@ -128,6 +131,12 @@ class Admin::AccountAction
queue_suspension_worker!
end
def handle_mark_unknown!
authorize(target_account, :mark_unknown?)
log_action(:mark_unknown, target_account.user)
target_account.mark_unknown!
end
def text_for_warning
[warning_preset&.text, text].compact.join("\n\n")
end

View File

@ -12,6 +12,7 @@
# reject_reports :boolean default(FALSE), not null
# force_sensitive :boolean default(FALSE), not null
# reason :text
# reject_unknown :boolean default(FALSE), not null
#
class DomainBlock < ApplicationRecord
@ -52,6 +53,7 @@ class DomainBlock < ApplicationRecord
additionals << "force sensitive media" if force_sensitive?
additionals << "reject media" if reject_media?
additionals << "reject reports" if reject_reports?
additionals << "reject unknown accounts" if reject_unknown?
additionals
end

View File

@ -13,6 +13,14 @@ class AccountPolicy < ApplicationPolicy
staff? && !record.user&.staff?
end
def mark_known?
staff?
end
def mark_unknown?
staff?
end
def suspend?
staff? && !record.user&.staff?
end

View File

@ -5,7 +5,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
include AutorejectHelper
# Should be called when uri has already been checked for locality
def call(uri, id: true, prefetched_body: nil, on_behalf_of: nil)
def call(uri, id: true, prefetched_body: nil, on_behalf_of: nil, announced_by: nil, requested: false)
return if autoreject?(uri)
@json = if prefetched_body.nil?
@ -24,7 +24,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
return if actor.nil? || actor.suspended?
ActivityPub::Activity.factory(activity_json, actor).perform
ActivityPub::Activity.factory(activity_json, actor, announced_by: announced_by, requested: requested).perform
end
private

View File

@ -5,8 +5,11 @@ class BlockDomainService < BaseService
def call(domain_block)
@domain_block = domain_block
@affected_status_ids = []
remove_existing_block!
process_domain_block!
invalidate_association_caches!
end
private
@ -16,8 +19,9 @@ class BlockDomainService < BaseService
end
def process_domain_block!
clear_media! if domain_block.reject_media?
clear_media! if domain_block.reject_media? || domain_block.suspend?
force_accounts_sensitive! if domain_block.force_sensitive?
mark_unknown_accounts! if domain_block.reject_unknown?
if domain_block.force_unlisted?
force_accounts_unlisted!
@ -39,17 +43,22 @@ class BlockDomainService < BaseService
def force_accounts_sensitive!
ApplicationRecord.transaction do
blocked_domain_accounts.in_batches.update_all(force_sensitive: true)
blocked_domain_accounts.reorder(nil).find_each do |account|
blocked_domain_accounts.find_each do |account|
@affected_status_ids |= account.statuses.where(sensitive: false).pluck(:id)
account.statuses.where(sensitive: false).in_batches.update_all(sensitive: true)
end
end
invalidate_association_caches! unless @affected_status_ids.blank?
end
def mark_unknown_accounts!
unknown_accounts.in_batches.update_all(known: false)
end
def force_accounts_unlisted!
ApplicationRecord.transaction do
blocked_domain_accounts.in_batches.update_all(force_unlisted: true)
blocked_domain_accounts.reorder(nil).find_each do |account|
blocked_domain_accounts.find_each do |account|
@affected_status_ids |= account.statuses.with_public_visibility.pluck(:id)
account.statuses.with_public_visibility.in_batches.update_all(visibility: :unlisted)
end
end
@ -60,23 +69,21 @@ class BlockDomainService < BaseService
end
def clear_media!
@affected_status_ids = []
clear_account_images!
clear_account_attachments!
clear_emojos!
invalidate_association_caches!
end
def suspend_accounts!
blocked_domain_accounts.without_suspended.reorder(nil).find_each do |account|
blocked_domain_accounts.without_suspended.find_each do |account|
SuspendAccountService.new.call(account, suspended_at: @domain_block.created_at)
end
end
def clear_account_images!
blocked_domain_accounts.reorder(nil).find_each do |account|
blocked_domain_accounts.find_each do |account|
account.avatar.destroy if account.avatar.exists?
account.header.destroy if account.header.exists?
account.save
@ -84,7 +91,7 @@ class BlockDomainService < BaseService
end
def clear_account_attachments!
media_from_blocked_domain.reorder(nil).find_each do |attachment|
media_from_blocked_domain.find_each do |attachment|
@affected_status_ids << attachment.status_id if attachment.status_id.present?
attachment.file.destroy if attachment.file.exists?
@ -102,7 +109,7 @@ class BlockDomainService < BaseService
end
def blocked_domain_accounts
Account.where(domain: blocked_domain)
Account.where(domain: blocked_domain).reorder(nil)
end
def media_from_blocked_domain
@ -112,4 +119,28 @@ class BlockDomainService < BaseService
def emojis_from_blocked_domains
CustomEmoji.where(domain: blocked_domain)
end
def unknown_accounts
Account.where(id: blocked_domain_accounts.pluck(:id) - known_account_ids).reorder(nil)
end
def known_account_ids
local_accounts | packmates | boosted_authors | faved_authors
end
def boosted_authors
Status.where(id: Status.local.reblogs.reorder(nil).select(:reblog_of_id)).reorder(nil).pluck(:account_id)
end
def faved_authors
Status.where(id: Favourite.select(:status_id)).reorder(nil).pluck(:account_id)
end
def local_accounts
Account.local.pluck(:id)
end
def packmates
Account.local.flat_map { |account| account.following_ids | account.follower_ids }
end
end

View File

@ -16,6 +16,8 @@ class FavouriteService < BaseService
favourite = Favourite.create!(account: account, status: status)
status.account.mark_known! unless status.account.known?
curate_status(status)
create_notification(favourite) unless skip_notify
bump_potential_friendship(account, status)

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class FetchRemoteStatusService < BaseService
def call(url, prefetched_body = nil)
def call(url, prefetched_body = nil, announced_by: nil, requested: false)
if prefetched_body.nil?
resource_url, resource_options = FetchAtomService.new.call(url)
else
@ -9,6 +9,9 @@ class FetchRemoteStatusService < BaseService
resource_options = { prefetched_body: prefetched_body }
end
resource_options[:announced_by] = announced_by unless announced_by.nil?
resource_options[:requested] = true if requested
return if resource_url.blank?
ActivityPub::FetchRemoteStatusService.new.call(resource_url, **resource_options)
end

View File

@ -14,6 +14,8 @@ class FollowService < BaseService
raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended?
raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account) || target_account.moved?
target_account.mark_known! unless target_account.known?
if source_account.following?(target_account)
# We're already following this account, but we'll call follow! again to
# make sure the reblogs status is set correctly.

View File

@ -94,6 +94,10 @@ class PostStatusService < BaseService
@in_reply_to.present? && @in_reply_to.reject_replies && @in_reply_to.account_id != @account.id
end
def mark_recipient_known
@in_reply_to.account.mark_known! unless @in_reply_to.account.known?
end
def set_footer_from_i_am
return if @footer.present? || @options[:no_footer]
name = @account.user.vars['_they:are']
@ -153,6 +157,7 @@ class PostStatusService < BaseService
limit_visibility_if_silenced
unless @in_reply_to.nil?
mark_recipient_known
inherit_reply_rejection
limit_visibility_to_reply
unfilter_thread_on_reply

View File

@ -17,6 +17,8 @@ class ReblogService < BaseService
new_reblog = reblog.nil?
if new_reblog
reblogged_status.account.mark_known! unless reblogged_status.account.known?
visibility = options[:visibility] || account.user&.setting_default_privacy
visibility = reblogged_status.visibility if reblogged_status.hidden?
reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility)

View File

@ -21,7 +21,7 @@ class ResolveURLService < BaseService
if equals_or_includes_any?(type, %w(Application Group Organization Person Service))
FetchRemoteAccountService.new.call(atom_url, body)
elsif equals_or_includes_any?(type, %w(Note Article Image Video Page Question))
FetchRemoteStatusService.new.call(atom_url, body)
FetchRemoteStatusService.new.call(atom_url, body, requested: true)
end
end

View File

@ -144,6 +144,11 @@
- if @account.local? && @account.user_approved?
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
- if @account.known?
= link_to t('admin.accounts.unknown'), mark_unknown_admin_account_path(@account.id), method: :post, class: 'button' if can?(:mark_unknown, @account)
- else
= link_to t('admin.accounts.known'), mark_known_admin_account_path(@account.id), method: :post, class: 'button' if can?(:mark_known, @account)
- if @account.force_sensitive?
= link_to t('admin.accounts.allow_nonsensitive'), allow_nonsensitive_admin_account_path(@account.id), method: :post, class: 'button' if can?(:allow_nonsensitive, @account)
- elsif !@account.local? || @account.user_approved?

View File

@ -14,6 +14,9 @@
.fields-group
= f.input :force_sensitive, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.force_sensitive'), hint: I18n.t('admin.domain_blocks.force_sensitive_hint')
.fields-group
= f.input :reject_unknown, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_unknown'), hint: I18n.t('admin.domain_blocks.reject_unknown_hint')
.fields-group
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint')

View File

@ -14,6 +14,9 @@
.fields-group
= f.input :force_sensitive, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.force_sensitive'), hint: I18n.t('admin.domain_blocks.force_sensitive_hint')
.fields-group
= f.input :reject_unknown, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_unknown'), hint: I18n.t('admin.domain_blocks.reject_unknown_hint')
.fields-group
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_media'), hint: I18n.t('admin.domain_blocks.reject_media_hint')

View File

@ -196,6 +196,8 @@ en:
unsubscribe: Unsubscribe
username: Username
warn: Warn
known: Mark known
unknown: Mark unknown
web: Web
action_logs:
actions:
@ -235,6 +237,8 @@ en:
update_custom_emoji: "%{name} updated emoji %{target}"
update_domain_block: "%{name} updated policy for %{target}"
update_status: "%{name} updated roar by %{target}"
mark_known_account: "%{name} marked %{target}'s account known"
mark_unknown_account: "%{name} marked %{target}'s account unknown"
deleted_status: "(deleted roar)"
title: Audit log
custom_emojis:
@ -305,6 +309,8 @@ en:
title: New domain policy
force_sensitive: Mark media sensitive
force_sensitive_hint: Forces all media from this domain to be marked sensitive.
reject_unknown: Reject unknown accounts
reject_unknown_hint: Rejects content and requests from accounts that haven't been interacted with by the community or immediate packmates.
reason: Add notes here.
reject_media: Reject media files
reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions

View File

@ -70,6 +70,7 @@ en:
none: Do nothing
force_sensitive: Force sensitive
force_unlisted: Force unlisted
mark_unknown: Mark unknown
silence: Silence
suspend: Suspend and irreversibly delete account data
warning_preset_id: Use a warning preset

View File

@ -189,6 +189,8 @@ Rails.application.routes.draw do
post :subscribe
post :unsubscribe
post :enable
post :mark_known
post :mark_unknown
post :force_sensitive
post :force_unlisted
post :allow_public

View File

@ -0,0 +1,5 @@
class AddKnownToAccounts < ActiveRecord::Migration[5.2]
def change
add_column :accounts, :known, :boolean, null: false, default: false
end
end

View File

@ -0,0 +1,30 @@
class MarkKnownAccounts < ActiveRecord::Migration[5.2]
def up
Rails.logger.info("Marking known accounts:")
known_accounts = local_accounts | packmates | boosted_authors | faved_authors
Rails.logger.info(" Updating account flags...")
Account.where(id: known_accounts).in_batches.update_all(known: true)
end
private
def boosted_authors
Rails.logger.info(" Gathering boosted accounts...")
Status.where(id: Status.local.reblogs.reorder(nil).select(:reblog_of_id)).reorder(nil).pluck(:account_id)
end
def faved_authors
Rails.logger.info(" Gathering favourited accounts...")
Status.where(id: Favourite.select(:status_id)).reorder(nil).pluck(:account_id)
end
def local_accounts
Rails.logger.info(" Gathering local accounts...")
Account.local.pluck(:id)
end
def packmates
Rails.logger.info(" Gathering packmate accounts...")
Account.local.flat_map { |account| account.following_ids | account.follower_ids }
end
end

View File

@ -0,0 +1,7 @@
class AddRejectUnknownToDomainBlocks < ActiveRecord::Migration[5.2]
def change
safety_assured {
add_column :domain_blocks, :reject_unknown, :boolean, null: false, default: false
}
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_08_06_195913) do
ActiveRecord::Schema.define(version: 2019_08_07_221924) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -154,6 +154,7 @@ ActiveRecord::Schema.define(version: 2019_08_06_195913) do
t.boolean "gently", default: false, null: false
t.boolean "kobold", default: false, null: false
t.boolean "froze"
t.boolean "known", default: false, null: false
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id"
@ -274,6 +275,7 @@ ActiveRecord::Schema.define(version: 2019_08_06_195913) do
t.boolean "reject_reports", default: false, null: false
t.boolean "force_sensitive", default: false, null: false
t.text "reason"
t.boolean "reject_unknown", default: false, null: false
t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true
end