From 83579695593c941ab39ab545c10a7f711bf715fc Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 15 Dec 2020 17:23:58 +0100 Subject: Fix admins being able to suspend their instance actor (#14567) * Fix admin being able to suspend their own instance account * Add text about the instance's own actor in admin view * Change instance actor notice from flash message to template * Do not list local instance actor in account moderation list --- app/models/account.rb | 3 ++- app/models/account_filter.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'app/models') diff --git a/app/models/account.rb b/app/models/account.rb index e21b353e9..80eb92a71 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -100,6 +100,7 @@ class Account < ApplicationRecord scope :sensitized, -> { where.not(sensitized_at: nil) } scope :without_suspended, -> { where(suspended_at: nil) } scope :without_silenced, -> { where(silenced_at: nil) } + scope :without_instance_actor, -> { where.not(id: -99) } scope :recent, -> { reorder(id: :desc) } scope :bots, -> { where(actor_type: %w(Application Service)) } scope :groups, -> { where(actor_type: 'Group') } @@ -222,7 +223,7 @@ class Account < ApplicationRecord end def suspended? - suspended_at.present? + suspended_at.present? && !instance_actor? end def suspended_permanently? diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index 7b6012e0f..2b001385f 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -45,7 +45,7 @@ class AccountFilter def scope_for(key, value) case key.to_s when 'local' - Account.local + Account.local.without_instance_actor when 'remote' Account.remote when 'by_domain' -- cgit From 8a95867693dfe072241d5ddcd0ba7ed8546eab1e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 18 Dec 2020 08:30:41 +0100 Subject: Add option to obfuscate domain name in public list of domain blocks (#15355) - Replace the middle of the domain with * characters (except for periods) - Add SHA-256 digest of the domain name in tooltip --- app/controllers/admin/domain_blocks_controller.rb | 4 ++-- app/models/domain_block.rb | 20 ++++++++++++++++++++ app/views/about/_domain_blocks.html.haml | 2 +- app/views/admin/domain_blocks/edit.html.haml | 3 +++ app/views/admin/domain_blocks/new.html.haml | 3 +++ config/locales/en.yml | 2 ++ .../20201218054746_add_obfuscate_to_domain_blocks.rb | 15 +++++++++++++++ db/schema.rb | 3 ++- 8 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20201218054746_add_obfuscate_to_domain_blocks.rb (limited to 'app/models') diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index 6a5b41a74..ba927b04a 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -74,11 +74,11 @@ module Admin end def update_params - params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment) + params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate) end def resource_params - params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment) + params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate) end end end diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index 829d7583b..bba04c603 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -12,6 +12,7 @@ # reject_reports :boolean default(FALSE), not null # private_comment :text # public_comment :text +# obfuscate :boolean default(FALSE), not null # class DomainBlock < ApplicationRecord @@ -73,4 +74,23 @@ class DomainBlock < ApplicationRecord scope = suspend? ? accounts.where(suspended_at: created_at) : accounts.where(silenced_at: created_at) scope.count end + + def public_domain + return domain unless obfuscate? + + length = domain.size + visible_ratio = length / 4 + + domain.chars.map.with_index do |chr, i| + if i > visible_ratio && i < length - visible_ratio && chr != '.' + '*' + else + chr + end + end.join + end + + def domain_digest + Digest::SHA256.hexdigest(domain) + end end diff --git a/app/views/about/_domain_blocks.html.haml b/app/views/about/_domain_blocks.html.haml index e0c5df41d..35a30f16e 100644 --- a/app/views/about/_domain_blocks.html.haml +++ b/app/views/about/_domain_blocks.html.haml @@ -7,6 +7,6 @@ - domain_blocks.each do |domain_block| %tr %td.nowrap - %span{ title: domain_block.domain }= domain_block.domain + %span{ title: "SHA-256: #{domain_block.domain_digest}" }= domain_block.public_domain %td = domain_block.public_comment if display_blocks_rationale? diff --git a/app/views/admin/domain_blocks/edit.html.haml b/app/views/admin/domain_blocks/edit.html.haml index d5868070a..6fe2edc82 100644 --- a/app/views/admin/domain_blocks/edit.html.haml +++ b/app/views/admin/domain_blocks/edit.html.haml @@ -20,6 +20,9 @@ .fields-group = f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint') + .fields-group + = f.input :obfuscate, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.obfuscate'), hint: I18n.t('admin.domain_blocks.obfuscate_hint') + .field-group = f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), rows: 6 diff --git a/app/views/admin/domain_blocks/new.html.haml b/app/views/admin/domain_blocks/new.html.haml index f503f9b77..8b78f71f2 100644 --- a/app/views/admin/domain_blocks/new.html.haml +++ b/app/views/admin/domain_blocks/new.html.haml @@ -20,6 +20,9 @@ .fields-group = f.input :reject_reports, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.reject_reports'), hint: I18n.t('admin.domain_blocks.reject_reports_hint') + .fields-group + = f.input :obfuscate, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_blocks.obfuscate'), hint: I18n.t('admin.domain_blocks.obfuscate_hint') + .field-group = f.input :private_comment, wrapper: :with_label, label: I18n.t('admin.domain_blocks.private_comment'), hint: t('admin.domain_blocks.private_comment_hint'), rows: 6 diff --git a/config/locales/en.yml b/config/locales/en.yml index 83f75b28f..e91f4344f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -402,6 +402,8 @@ en: silence: Silence suspend: Suspend title: New domain block + obfuscate: Obfuscate domain name + obfuscate_hint: Partially obfuscate the domain name in the list if advertising the list of domain limitations is enabled private_comment: Private comment private_comment_hint: Comment about this domain limitation for internal use by the moderators. public_comment: Public comment diff --git a/db/migrate/20201218054746_add_obfuscate_to_domain_blocks.rb b/db/migrate/20201218054746_add_obfuscate_to_domain_blocks.rb new file mode 100644 index 000000000..26f4ddb85 --- /dev/null +++ b/db/migrate/20201218054746_add_obfuscate_to_domain_blocks.rb @@ -0,0 +1,15 @@ +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddObfuscateToDomainBlocks < ActiveRecord::Migration[5.2] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def up + safety_assured { add_column_with_default :domain_blocks, :obfuscate, :boolean, default: false, allow_null: false } + end + + def down + remove_column :domain_blocks, :obfuscate + end +end diff --git a/db/schema.rb b/db/schema.rb index 55822a4b3..18bf1d4ca 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_12_06_004238) do +ActiveRecord::Schema.define(version: 2020_12_18_054746) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -360,6 +360,7 @@ ActiveRecord::Schema.define(version: 2020_12_06_004238) do t.boolean "reject_reports", default: false, null: false t.text "private_comment" t.text "public_comment" + t.boolean "obfuscate", default: false, null: false t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true end -- cgit From eb35be0431b2cdd2bbf3339beb9c5a0839e1088b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 18 Dec 2020 09:18:31 +0100 Subject: Fix follow limit preventing re-following of a moved account (#14207) --- app/models/follow.rb | 2 +- app/models/follow_request.rb | 2 +- app/models/import.rb | 1 + app/services/import_service.rb | 7 ++---- app/validators/import_validator.rb | 44 ++++++++++++++++++++++++++++++++++++++ config/locales/en.yml | 2 ++ spec/models/follow_spec.rb | 2 +- spec/models/import_spec.rb | 10 +++++++++ 8 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 app/validators/import_validator.rb (limited to 'app/models') diff --git a/app/models/follow.rb b/app/models/follow.rb index 55a9da792..69a1722b3 100644 --- a/app/models/follow.rb +++ b/app/models/follow.rb @@ -26,7 +26,7 @@ class Follow < ApplicationRecord has_one :notification, as: :activity, dependent: :destroy validates :account_id, uniqueness: { scope: :target_account_id } - validates_with FollowLimitValidator, on: :create + validates_with FollowLimitValidator, on: :create, if: :rate_limit? scope :recent, -> { reorder(id: :desc) } diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb index c1f19149b..2d2a77b59 100644 --- a/app/models/follow_request.rb +++ b/app/models/follow_request.rb @@ -26,7 +26,7 @@ class FollowRequest < ApplicationRecord has_one :notification, as: :activity, dependent: :destroy validates :account_id, uniqueness: { scope: :target_account_id } - validates_with FollowLimitValidator, on: :create + validates_with FollowLimitValidator, on: :create, if: :rate_limit? def authorize! account.follow!(target_account, reblogs: show_reblogs, notify: notify, uri: uri) diff --git a/app/models/import.rb b/app/models/import.rb index 702453289..00a54892e 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -27,6 +27,7 @@ class Import < ApplicationRecord enum type: [:following, :blocking, :muting, :domain_blocking, :bookmarks] validates :type, presence: true + validates_with ImportValidator, on: :create has_attached_file :data validates_attachment_content_type :data, content_type: FILE_TYPES diff --git a/app/services/import_service.rb b/app/services/import_service.rb index 288e47f1e..0c6ef2238 100644 --- a/app/services/import_service.rb +++ b/app/services/import_service.rb @@ -27,7 +27,7 @@ class ImportService < BaseService def import_follows! parse_import_data!(['Account address']) - import_relationships!('follow', 'unfollow', @account.following, follow_limit, reblogs: { header: 'Show boosts', default: true }) + import_relationships!('follow', 'unfollow', @account.following, ROWS_PROCESSING_LIMIT, reblogs: { header: 'Show boosts', default: true }) end def import_blocks! @@ -85,6 +85,7 @@ class ImportService < BaseService head_items = items.uniq { |acct, _| acct.split('@')[1] } tail_items = items - head_items + Import::RelationshipWorker.push_bulk(head_items + tail_items) do |acct, extra| [@account.id, acct, action, extra] end @@ -133,10 +134,6 @@ class ImportService < BaseService Paperclip.io_adapters.for(@import.data).read end - def follow_limit - FollowLimitValidator.limit_for_account(@account) - end - def relations_map_for_account(account, account_ids) { blocking: {}, diff --git a/app/validators/import_validator.rb b/app/validators/import_validator.rb new file mode 100644 index 000000000..a182abfa5 --- /dev/null +++ b/app/validators/import_validator.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +class ImportValidator < ActiveModel::Validator + KNOWN_HEADERS = [ + 'Account address', + '#domain', + '#uri', + ].freeze + + def validate(import) + return if import.type.blank? || import.data.blank? + + # We parse because newlines could be part of individual rows. This + # runs on create so we should be reading the local file here before + # it is uploaded to object storage or moved anywhere... + csv_data = CSV.parse(import.data.queued_for_write[:original].read) + + row_count = csv_data.size + row_count -= 1 if KNOWN_HEADERS.include?(csv_data.first&.first) + + import.errors.add(:data, I18n.t('imports.errors.over_rows_processing_limit', count: ImportService::ROWS_PROCESSING_LIMIT)) if row_count > ImportService::ROWS_PROCESSING_LIMIT + + case import.type + when 'following' + validate_following_import(import, row_count) + end + end + + private + + def validate_following_import(import, row_count) + base_limit = FollowLimitValidator.limit_for_account(import.account) + + limit = begin + if import.overwrite? + base_limit + else + base_limit - import.account.following_count + end + end + + import.errors.add(:data, I18n.t('users.follow_limit_reached', limit: base_limit)) if row_count > limit + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index e91f4344f..18a207f5e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -923,6 +923,8 @@ en: status: Verification status view_proof: View proof imports: + errors: + over_rows_processing_limit: contains more than %{count} rows modes: merge: Merge merge_long: Keep existing records and add new ones diff --git a/spec/models/follow_spec.rb b/spec/models/follow_spec.rb index 0c84e5e7b..e723a1ef2 100644 --- a/spec/models/follow_spec.rb +++ b/spec/models/follow_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Follow, type: :model do let(:bob) { Fabricate(:account, username: 'bob') } describe 'validations' do - subject { Follow.new(account: alice, target_account: bob) } + subject { Follow.new(account: alice, target_account: bob, rate_limit: true) } it 'has a valid fabricator' do follow = Fabricate.build(:follow) diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb index 321761166..a5eec1722 100644 --- a/spec/models/import_spec.rb +++ b/spec/models/import_spec.rb @@ -20,5 +20,15 @@ RSpec.describe Import, type: :model do import = Import.create(account: account, type: type) expect(import).to model_have_error_on_field(:data) end + + it 'is invalid with too many rows in data' do + import = Import.create(account: account, type: type, data: StringIO.new("foo@bar.com\n" * (ImportService::ROWS_PROCESSING_LIMIT + 10))) + expect(import).to model_have_error_on_field(:data) + end + + it 'is invalid when there are more rows when following limit' do + import = Import.create(account: account, type: type, data: StringIO.new("foo@bar.com\n" * (FollowLimitValidator.limit_for_account(account) + 10))) + expect(import).to model_have_error_on_field(:data) + end end end -- cgit