Change: #591 ホワイトリストのドメイン一覧の保存先・画面変更 (#689)

* Change: #591 ホワイトリストのドメイン一覧の保存先・画面変更

* Update account_batch.rb

* 表示まわりを改善

* Update dangerous.rake
This commit is contained in:
KMY(雪あすか) 2024-04-03 12:09:43 +09:00 committed by GitHub
parent 8c399cefce
commit ff2860d0df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 211 additions and 49 deletions

View file

@ -32,4 +32,5 @@ linters:
- 'app/views/application/_sidebar.html.haml' - 'app/views/application/_sidebar.html.haml'
- 'app/views/admin/ng_rules/_ng_rule_fields.html.haml' - 'app/views/admin/ng_rules/_ng_rule_fields.html.haml'
- 'app/views/admin/ng_words/keywords/_ng_word.html.haml' - 'app/views/admin/ng_words/keywords/_ng_word.html.haml'
- 'app/views/admin/ng_words/white_list/_specified_domain.html.haml'
- 'app/views/admin/sensitive_words/_sensitive_word.html.haml' - 'app/views/admin/sensitive_words/_sensitive_word.html.haml'

View file

@ -2,10 +2,33 @@
module Admin module Admin
class NgWords::WhiteListController < NgWordsController class NgWords::WhiteListController < NgWordsController
def show
super
@white_list_domains = SpecifiedDomain.white_list_domain_caches.presence || [SpecifiedDomain.new]
end
protected protected
def validate
begin
SpecifiedDomain.save_from_raws_as_white_list(settings_params_list)
return true
rescue
flash[:alert] = I18n.t('admin.ng_words.save_error')
redirect_to after_update_redirect_path
end
false
end
def after_update_redirect_path def after_update_redirect_path
admin_ng_words_white_list_path admin_ng_words_white_list_path
end end
private
def settings_params_list
params.require(:form_admin_settings)[:specified_domains]
end
end end
end end

View file

@ -316,40 +316,21 @@ const removeTableRow = (target: EventTarget | null, tableId: string) => {
tableElement.removeChild(tableRowElement); tableElement.removeChild(tableRowElement);
}; };
Rails.delegate( const setupTableList = (id: string) => {
document, Rails.delegate(document, `#${id} .add-row-button`, 'click', (ev) => {
'#sensitive-words-table .add-row-button',
'click',
(ev) => {
ev.preventDefault(); ev.preventDefault();
addTableRow('sensitive-words-table'); addTableRow(id);
}, });
);
Rails.delegate( Rails.delegate(document, `#${id} .delete-row-button`, 'click', (ev) => {
document,
'#sensitive-words-table .delete-row-button',
'click',
(ev) => {
ev.preventDefault(); ev.preventDefault();
removeTableRow(ev.target, 'sensitive-words-table'); removeTableRow(ev.target, id);
}, });
); };
Rails.delegate(document, '#ng-words-table .add-row-button', 'click', (ev) => { setupTableList('sensitive-words-table');
ev.preventDefault(); setupTableList('ng-words-table');
addTableRow('ng-words-table'); setupTableList('white-list-table');
});
Rails.delegate(
document,
'#ng-words-table .delete-row-button',
'click',
(ev) => {
ev.preventDefault();
removeTableRow(ev.target, 'ng-words-table');
},
);
async function mountReactComponent(element: Element) { async function mountReactComponent(element: Element) {
const componentName = element.getAttribute('data-admin-component'); const componentName = element.getAttribute('data-admin-component');

View file

@ -98,10 +98,10 @@ class Form::AccountBatch
def approve_remote_domain! def approve_remote_domain!
domains = accounts.group_by(&:domain).pluck(0) domains = accounts.group_by(&:domain).pluck(0)
if (Setting.permit_new_account_domains || []).compact_blank.present? (domains - SpecifiedDomain.where(domain: domains, table: 0).pluck(:domain)).each do |domain|
list = ((Setting.permit_new_account_domains || []) + domains).compact_blank.uniq.join("\n") SpecifiedDomain.create!(domain: domain, table: 0)
Form::AdminSettings.new(permit_new_account_domains: list).save
end end
Account.where(domain: domains, remote_pending: true).find_each do |account| Account.where(domain: domains, remote_pending: true).find_each do |account|
approve_remote_account(account) approve_remote_account(account)
end end

View file

@ -61,7 +61,6 @@ class Form::AdminSettings
unlocked_friend unlocked_friend
enable_local_timeline enable_local_timeline
emoji_reaction_disallow_domains emoji_reaction_disallow_domains
permit_new_account_domains
block_unfollow_account_mention block_unfollow_account_mention
hold_remote_new_accounts hold_remote_new_accounts
).freeze ).freeze
@ -121,7 +120,6 @@ class Form::AdminSettings
STRING_ARRAY_KEYS = %i( STRING_ARRAY_KEYS = %i(
emoji_reaction_disallow_domains emoji_reaction_disallow_domains
permit_new_account_domains
).freeze ).freeze
attr_accessor(*KEYS) attr_accessor(*KEYS)

View file

@ -0,0 +1,78 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: specified_domains
#
# id :bigint(8) not null, primary key
# domain :string not null
# table :integer default(0), not null
# options :jsonb not null
# created_at :datetime not null
# updated_at :datetime not null
#
class SpecifiedDomain < ApplicationRecord
attr_accessor :domains
validates :domain, uniqueness: { scope: :table }
after_commit :invalidate_cache!
scope :white_list_domains, -> { where(table: 0) }
class << self
def white_list_domain_caches
Rails.cache.fetch('specified_domains:white_list') { white_list_domains.to_a }
end
def save_from_hashes(rows, type, caches)
unmatched = caches
matched = []
SpecifiedDomain.transaction do
rows.filter { |item| item[:domain].present? }.each do |item|
exists = unmatched.find { |i| i.domain == item[:domain] }
if exists.present?
unmatched.delete(exists)
matched << exists
next unless item.key?(:options) && item[:options] == exists.options
exists.update!(options: item[:options])
elsif matched.none? { |i| i.domain == item[:domain] }
SpecifiedDomain.create!(
domain: item[:domain],
table: type,
options: item[:options] || {}
)
end
end
SpecifiedDomain.destroy(unmatched.map(&:id))
end
true
end
def save_from_raws(rows, type, caches)
hashes = (rows['domains'] || []).map do |domain|
{
domain: domain,
type: type,
}
end
save_from_hashes(hashes, type, caches)
end
def save_from_raws_as_white_list(rows)
save_from_raws(rows, 0, white_list_domain_caches)
end
end
private
def invalidate_cache!
Rails.cache.delete('specified_domains:white_list')
end
end

View file

@ -142,11 +142,7 @@ class ActivityPub::ProcessAccountService < BaseService
def blocking_new_account? def blocking_new_account?
return false unless Setting.hold_remote_new_accounts return false unless Setting.hold_remote_new_accounts
permit_new_account_domains.exclude?(@domain) SpecifiedDomain.white_list_domain_caches.none? { |item| item.domain == @domain }
end
def permit_new_account_domains
(Setting.permit_new_account_domains || []).compact_blank
end end
def valid_account? def valid_account?

View file

@ -0,0 +1,6 @@
- temporary_id = defined?(@temp_id) ? @temp_id += 1 : @temp_id = 1
%tr{ class: template ? 'template-row' : nil }
%td= f.input :domains, as: :string, input_html: { multiple: true, value: specified_domain.domain }
%td
= hidden_field_tag :'form_admin_settings[specified_domains][temporary_ids][]', temporary_id, class: 'temporary_id'
= link_to safe_join([fa_icon('times'), t('filters.index.delete')]), '#', class: 'table-action-link delete-row-button'

View file

@ -18,8 +18,24 @@
.fields-group .fields-group
= f.input :hold_remote_new_accounts, wrapper: :with_label, as: :boolean, label: t('admin.ng_words.hold_remote_new_accounts'), hint: t('admin.ng_words.remote_approval_hint') = f.input :hold_remote_new_accounts, wrapper: :with_label, as: :boolean, label: t('admin.ng_words.hold_remote_new_accounts'), hint: t('admin.ng_words.remote_approval_hint')
.fields-group %h4= t('admin.ng_words.white_list_header')
= f.input :permit_new_account_domains, wrapper: :with_label, as: :text, kmyblue: true, input_html: { rows: 6 }, label: t('admin.ng_words.permit_new_account_domains')
.table-wrapper
%table.table.keywords-table#white-list-table
%thead
%tr
%th= t('simple_form.labels.defaults.domain')
%th
%tbody
= f.simple_fields_for :specified_domains, @white_list_domains do |domain|
= render partial: 'specified_domain', collection: @white_list_domains, locals: { f: domain, template: false }
= f.simple_fields_for :specified_domains, @white_list_domains do |domain|
= render partial: 'specified_domain', collection: [SpecifiedDomain.new], locals: { f: domain, template: true }
%tfoot
%tr
%td{ colspan: 2 }
= link_to safe_join([fa_icon('plus'), t('admin.ng_words.edit.add_domain')]), '#', class: 'table-action-link add-row-button'
.actions .actions
= f.button :button, t('generic.save_changes'), type: :submit = f.button :button, t('generic.save_changes'), type: :submit

View file

@ -749,11 +749,12 @@ en:
block_unfollow_account_mention_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。NGルールで代替してください。 block_unfollow_account_mention_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。NGルールで代替してください。
deprecated: Will remove settings deprecated: Will remove settings
deprecated_hint: These settings will be removed in the next LTS or kmyblue version 14.0, whichever comes first. Please refer to the description of each setting and replace them with the new settings if possible. deprecated_hint: These settings will be removed in the next LTS or kmyblue version 14.0, whichever comes first. Please refer to the description of each setting and replace them with the new settings if possible.
edit:
add_domain: Add domain
hide_local_users_for_anonymous: Hide timeline local user posts from anonymous hide_local_users_for_anonymous: Hide timeline local user posts from anonymous
hide_local_users_for_anonymous_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。サーバー設定の「見つける」にある「公開タイムラインへの未認証のアクセスを許可する」で、完全ではありませんが代替可能です。 hide_local_users_for_anonymous_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。サーバー設定の「見つける」にある「公開タイムラインへの未認証のアクセスを許可する」で、完全ではありませんが代替可能です。
hold_remote_new_accounts: Hold new remote accounts hold_remote_new_accounts: Hold new remote accounts
keywords: Reject keywords keywords: Reject keywords
permit_new_account_domains: Domain list to automatically approve new users
preamble: This setting is useful for solving problems related to spam that are difficult to address with domain blocking. You can reject posts that meet certain criteria, such as the inclusion of specific keywords. Please consider your settings carefully and check your history regularly to ensure that problem-free submissions are not deleted. preamble: This setting is useful for solving problems related to spam that are difficult to address with domain blocking. You can reject posts that meet certain criteria, such as the inclusion of specific keywords. Please consider your settings carefully and check your history regularly to ensure that problem-free submissions are not deleted.
post_hash_tags_max: Hash tags max for posts post_hash_tags_max: Hash tags max for posts
post_mentions_max: Mentions max for posts post_mentions_max: Mentions max for posts
@ -771,6 +772,7 @@ en:
test_error: Testing is returned any errors test_error: Testing is returned any errors
title: NG words and against spams title: NG words and against spams
white_list: White list white_list: White list
white_list_header: List of domains for immediate approval of remote accounts
white_list_hint: Whitelisting can be used as a last resort when exposed to severe attacks. External attacks do not disappear immediately, but they can be reduced gradually and reliably through moderation. In addition, a regular remote account approval process is required. white_list_hint: Whitelisting can be used as a last resort when exposed to severe attacks. External attacks do not disappear immediately, but they can be reduced gradually and reliably through moderation. In addition, a regular remote account approval process is required.
ngword_histories: ngword_histories:
back_to_ng_words: NG words and against spams back_to_ng_words: NG words and against spams

View file

@ -748,11 +748,12 @@ ja:
block_unfollow_account_mention_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。NGルールで代替してください。 block_unfollow_account_mention_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。NGルールで代替してください。
deprecated: 新しいバージョンで削除する予定の設定 deprecated: 新しいバージョンで削除する予定の設定
deprecated_hint: これらの設定は、次回のLTS、またはkmyblueバージョン14.0のどちらか早い方で削除する予定です。それぞれの設定の説明を参照して、可能であれば新しい設定に置き換えてください。 deprecated_hint: これらの設定は、次回のLTS、またはkmyblueバージョン14.0のどちらか早い方で削除する予定です。それぞれの設定の説明を参照して、可能であれば新しい設定に置き換えてください。
edit:
add_domain: ドメインを追加
hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする
hide_local_users_for_anonymous_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。サーバー設定の「見つける」にある「公開タイムラインへの未認証のアクセスを許可する」で、完全ではありませんが代替可能です。 hide_local_users_for_anonymous_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。サーバー設定の「見つける」にある「公開タイムラインへの未認証のアクセスを許可する」で、完全ではありませんが代替可能です。
hold_remote_new_accounts: リモートの新規アカウントを保留する hold_remote_new_accounts: リモートの新規アカウントを保留する
keywords: 拒否するキーワード keywords: 拒否するキーワード
permit_new_account_domains: 新規ユーザーを自動承認するドメイン
phrases: phrases:
regexp_html: <strong>正規</strong> 表現 にチェックの入っている項目は、正規表現を用いての比較となります。 regexp_html: <strong>正規</strong> 表現 にチェックの入っている項目は、正規表現を用いての比較となります。
regexp_short: 正規 regexp_short: 正規
@ -770,6 +771,7 @@ ja:
test_error: NGワードのテストに失敗しました。正規表現のミスが含まれているかもしれません test_error: NGワードのテストに失敗しました。正規表現のミスが含まれているかもしれません
title: NGワードとスパム title: NGワードとスパム
white_list: ホワイトリスト white_list: ホワイトリスト
white_list_header: リモートアカウントを即座に承認するドメイン一覧
white_list_hint: 激しい攻撃に晒された場合の最終手段として、ホワイトリストが利用できます。外部からの攻撃が即座に消えるわけではありませんが、モデレーションを進めることで徐々に確実に減らすことができます。また、定期的なリモートアカウント承認作業が求められます。 white_list_hint: 激しい攻撃に晒された場合の最終手段として、ホワイトリストが利用できます。外部からの攻撃が即座に消えるわけではありませんが、モデレーションを進めることで徐々に確実に減らすことができます。また、定期的なリモートアカウント承認作業が求められます。
ngword_histories: ngword_histories:
back_to_ng_words: NGワードとスパム back_to_ng_words: NGワードとスパム

View file

@ -218,6 +218,7 @@ en:
discoverable: Suggest account to others discoverable: Suggest account to others
discoverable_local: Disallow suggesting account on other servers discoverable_local: Disallow suggesting account on other servers
display_name: Display name display_name: Display name
domain: Domain
email: E-mail address email: E-mail address
expires_in: Expire after expires_in: Expire after
fields: Extra fields fields: Extra fields

View file

@ -229,6 +229,7 @@ ja:
discoverable: ディレクトリに掲載する discoverable: ディレクトリに掲載する
discoverable_local: 他サーバーのディレクトリに掲載しない discoverable_local: 他サーバーのディレクトリに掲載しない
display_name: 表示名 display_name: 表示名
domain: ドメイン
email: メールアドレス email: メールアドレス
expires_in: 有効期限 expires_in: 有効期限
fields: プロフィール補足情報 fields: プロフィール補足情報

View file

@ -0,0 +1,41 @@
# frozen_string_literal: true
class CreateSpecifiedDomains < ActiveRecord::Migration[7.1]
class Setting < ApplicationRecord
def value
YAML.safe_load(self[:value], permitted_classes: [ActiveSupport::HashWithIndifferentAccess, Symbol]) if self[:value].present?
end
def value=(new_value)
self[:value] = new_value.to_yaml
end
end
class SpecifiedDomain < ApplicationRecord; end
def up
create_table :specified_domains do |t|
t.string :domain, null: false
t.integer :table, default: 0, null: false
t.jsonb :options, null: false, default: {}
t.timestamps
end
add_index :specified_domains, %i(domain table), unique: true
setting = Setting.find_by(var: :permit_new_account_domains)
(setting&.value || []).compact.uniq.each do |domain|
SpecifiedDomain.create!(domain: domain, table: 0)
end
setting&.destroy
end
def down
Setting.find_by(var: :permit_new_account_domains)&.destroy
Setting.new(var: :permit_new_account_domains).tap { |s| s.value = SpecifiedDomain.where(table: 0).pluck(:domain) }.save!
drop_table :specified_domains
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2024_03_27_234026) do ActiveRecord::Schema[7.1].define(version: 2024_04_01_222541) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -1328,6 +1328,15 @@ ActiveRecord::Schema[7.1].define(version: 2024_03_27_234026) do
t.index ["version"], name: "index_software_updates_on_version", unique: true t.index ["version"], name: "index_software_updates_on_version", unique: true
end end
create_table "specified_domains", force: :cascade do |t|
t.string "domain", null: false
t.integer "table", default: 0, null: false
t.jsonb "options", default: {}, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["domain", "table"], name: "index_specified_domains_on_domain_and_table", unique: true
end
create_table "status_capability_tokens", force: :cascade do |t| create_table "status_capability_tokens", force: :cascade do |t|
t.bigint "status_id", null: false t.bigint "status_id", null: false
t.string "token" t.string "token"

View file

@ -97,6 +97,7 @@ namespace :dangerous do
20240320231633 20240320231633
20240326231854 20240326231854
20240327234026 20240327234026
20240401222541
) )
# Removed: account_groups # Removed: account_groups
target_tables = %w( target_tables = %w(
@ -121,6 +122,7 @@ namespace :dangerous do
pending_statuses pending_statuses
scheduled_expiration_statuses scheduled_expiration_statuses
sensitive_words sensitive_words
specified_domains
status_capability_tokens status_capability_tokens
status_references status_references
) )

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
Fabricator(:specified_domain) do
domain { sequence(:domain) { |i| "example_#{i}.com" } }
end

View file

@ -13,7 +13,7 @@ RSpec.describe ActivityPub::ProcessAccountService do
subject { described_class.new.call('alice', 'example.com', payload) } subject { described_class.new.call('alice', 'example.com', payload) }
let(:hold_remote_new_accounts) { true } let(:hold_remote_new_accounts) { true }
let(:permit_new_account_domains) { nil } let(:permit_domain) { nil }
let(:payload) do let(:payload) do
{ {
id: 'https://foo.test', id: 'https://foo.test',
@ -26,7 +26,7 @@ RSpec.describe ActivityPub::ProcessAccountService do
before do before do
Setting.hold_remote_new_accounts = hold_remote_new_accounts Setting.hold_remote_new_accounts = hold_remote_new_accounts
Setting.permit_new_account_domains = permit_new_account_domains Fabricate(:specified_domain, domain: permit_domain, table: 0) if permit_domain
end end
it 'creates pending account in a simple case' do it 'creates pending account in a simple case' do
@ -37,7 +37,7 @@ RSpec.describe ActivityPub::ProcessAccountService do
end end
context 'when is blocked' do context 'when is blocked' do
let(:permit_new_account_domains) { ['foo.bar'] } let(:permit_domain) { 'foo.bar' }
it 'creates pending account' do it 'creates pending account' do
expect(subject).to_not be_nil expect(subject).to_not be_nil
@ -98,7 +98,7 @@ RSpec.describe ActivityPub::ProcessAccountService do
end end
context 'when is in whitelist' do context 'when is in whitelist' do
let(:permit_new_account_domains) { ['example.com'] } let(:permit_domain) { 'example.com' }
it 'does not create account' do it 'does not create account' do
expect(subject).to_not be_nil expect(subject).to_not be_nil