* Change: #591 ホワイトリストのドメイン一覧の保存先・画面変更 * Update account_batch.rb * 表示まわりを改善 * Update dangerous.rake
This commit is contained in:
parent
8c399cefce
commit
ff2860d0df
18 changed files with 211 additions and 49 deletions
|
@ -32,4 +32,5 @@ linters:
|
|||
- 'app/views/application/_sidebar.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/white_list/_specified_domain.html.haml'
|
||||
- 'app/views/admin/sensitive_words/_sensitive_word.html.haml'
|
||||
|
|
|
@ -2,10 +2,33 @@
|
|||
|
||||
module Admin
|
||||
class NgWords::WhiteListController < NgWordsController
|
||||
def show
|
||||
super
|
||||
@white_list_domains = SpecifiedDomain.white_list_domain_caches.presence || [SpecifiedDomain.new]
|
||||
end
|
||||
|
||||
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
|
||||
admin_ng_words_white_list_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def settings_params_list
|
||||
params.require(:form_admin_settings)[:specified_domains]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -316,40 +316,21 @@ const removeTableRow = (target: EventTarget | null, tableId: string) => {
|
|||
tableElement.removeChild(tableRowElement);
|
||||
};
|
||||
|
||||
Rails.delegate(
|
||||
document,
|
||||
'#sensitive-words-table .add-row-button',
|
||||
'click',
|
||||
(ev) => {
|
||||
const setupTableList = (id: string) => {
|
||||
Rails.delegate(document, `#${id} .add-row-button`, 'click', (ev) => {
|
||||
ev.preventDefault();
|
||||
addTableRow('sensitive-words-table');
|
||||
},
|
||||
);
|
||||
addTableRow(id);
|
||||
});
|
||||
|
||||
Rails.delegate(
|
||||
document,
|
||||
'#sensitive-words-table .delete-row-button',
|
||||
'click',
|
||||
(ev) => {
|
||||
Rails.delegate(document, `#${id} .delete-row-button`, 'click', (ev) => {
|
||||
ev.preventDefault();
|
||||
removeTableRow(ev.target, 'sensitive-words-table');
|
||||
},
|
||||
);
|
||||
removeTableRow(ev.target, id);
|
||||
});
|
||||
};
|
||||
|
||||
Rails.delegate(document, '#ng-words-table .add-row-button', 'click', (ev) => {
|
||||
ev.preventDefault();
|
||||
addTableRow('ng-words-table');
|
||||
});
|
||||
|
||||
Rails.delegate(
|
||||
document,
|
||||
'#ng-words-table .delete-row-button',
|
||||
'click',
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
removeTableRow(ev.target, 'ng-words-table');
|
||||
},
|
||||
);
|
||||
setupTableList('sensitive-words-table');
|
||||
setupTableList('ng-words-table');
|
||||
setupTableList('white-list-table');
|
||||
|
||||
async function mountReactComponent(element: Element) {
|
||||
const componentName = element.getAttribute('data-admin-component');
|
||||
|
|
|
@ -98,10 +98,10 @@ class Form::AccountBatch
|
|||
|
||||
def approve_remote_domain!
|
||||
domains = accounts.group_by(&:domain).pluck(0)
|
||||
if (Setting.permit_new_account_domains || []).compact_blank.present?
|
||||
list = ((Setting.permit_new_account_domains || []) + domains).compact_blank.uniq.join("\n")
|
||||
Form::AdminSettings.new(permit_new_account_domains: list).save
|
||||
(domains - SpecifiedDomain.where(domain: domains, table: 0).pluck(:domain)).each do |domain|
|
||||
SpecifiedDomain.create!(domain: domain, table: 0)
|
||||
end
|
||||
|
||||
Account.where(domain: domains, remote_pending: true).find_each do |account|
|
||||
approve_remote_account(account)
|
||||
end
|
||||
|
|
|
@ -61,7 +61,6 @@ class Form::AdminSettings
|
|||
unlocked_friend
|
||||
enable_local_timeline
|
||||
emoji_reaction_disallow_domains
|
||||
permit_new_account_domains
|
||||
block_unfollow_account_mention
|
||||
hold_remote_new_accounts
|
||||
).freeze
|
||||
|
@ -121,7 +120,6 @@ class Form::AdminSettings
|
|||
|
||||
STRING_ARRAY_KEYS = %i(
|
||||
emoji_reaction_disallow_domains
|
||||
permit_new_account_domains
|
||||
).freeze
|
||||
|
||||
attr_accessor(*KEYS)
|
||||
|
|
78
app/models/specified_domain.rb
Normal file
78
app/models/specified_domain.rb
Normal 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
|
|
@ -142,11 +142,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
|||
def blocking_new_account?
|
||||
return false unless Setting.hold_remote_new_accounts
|
||||
|
||||
permit_new_account_domains.exclude?(@domain)
|
||||
end
|
||||
|
||||
def permit_new_account_domains
|
||||
(Setting.permit_new_account_domains || []).compact_blank
|
||||
SpecifiedDomain.white_list_domain_caches.none? { |item| item.domain == @domain }
|
||||
end
|
||||
|
||||
def valid_account?
|
||||
|
|
|
@ -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'
|
|
@ -18,8 +18,24 @@
|
|||
.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')
|
||||
|
||||
.fields-group
|
||||
= 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')
|
||||
%h4= t('admin.ng_words.white_list_header')
|
||||
|
||||
.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
|
||||
= f.button :button, t('generic.save_changes'), type: :submit
|
||||
|
|
|
@ -749,11 +749,12 @@ en:
|
|||
block_unfollow_account_mention_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。NGルールで代替してください。
|
||||
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.
|
||||
edit:
|
||||
add_domain: Add domain
|
||||
hide_local_users_for_anonymous: Hide timeline local user posts from anonymous
|
||||
hide_local_users_for_anonymous_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。サーバー設定の「見つける」にある「公開タイムラインへの未認証のアクセスを許可する」で、完全ではありませんが代替可能です。
|
||||
hold_remote_new_accounts: Hold new remote accounts
|
||||
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.
|
||||
post_hash_tags_max: Hash tags max for posts
|
||||
post_mentions_max: Mentions max for posts
|
||||
|
@ -771,6 +772,7 @@ en:
|
|||
test_error: Testing is returned any errors
|
||||
title: NG words and against spams
|
||||
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.
|
||||
ngword_histories:
|
||||
back_to_ng_words: NG words and against spams
|
||||
|
|
|
@ -748,11 +748,12 @@ ja:
|
|||
block_unfollow_account_mention_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。NGルールで代替してください。
|
||||
deprecated: 新しいバージョンで削除する予定の設定
|
||||
deprecated_hint: これらの設定は、次回のLTS、またはkmyblueバージョン14.0のどちらか早い方で削除する予定です。それぞれの設定の説明を参照して、可能であれば新しい設定に置き換えてください。
|
||||
edit:
|
||||
add_domain: ドメインを追加
|
||||
hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする
|
||||
hide_local_users_for_anonymous_hint: この設定は削除予定です。設定削除後は、常にチェックをつけていない場合と同じ挙動になります。サーバー設定の「見つける」にある「公開タイムラインへの未認証のアクセスを許可する」で、完全ではありませんが代替可能です。
|
||||
hold_remote_new_accounts: リモートの新規アカウントを保留する
|
||||
keywords: 拒否するキーワード
|
||||
permit_new_account_domains: 新規ユーザーを自動承認するドメイン
|
||||
phrases:
|
||||
regexp_html: <strong>正規</strong> 表現 にチェックの入っている項目は、正規表現を用いての比較となります。
|
||||
regexp_short: 正規
|
||||
|
@ -770,6 +771,7 @@ ja:
|
|||
test_error: NGワードのテストに失敗しました。正規表現のミスが含まれているかもしれません
|
||||
title: NGワードとスパム
|
||||
white_list: ホワイトリスト
|
||||
white_list_header: リモートアカウントを即座に承認するドメイン一覧
|
||||
white_list_hint: 激しい攻撃に晒された場合の最終手段として、ホワイトリストが利用できます。外部からの攻撃が即座に消えるわけではありませんが、モデレーションを進めることで徐々に確実に減らすことができます。また、定期的なリモートアカウント承認作業が求められます。
|
||||
ngword_histories:
|
||||
back_to_ng_words: NGワードとスパム
|
||||
|
|
|
@ -218,6 +218,7 @@ en:
|
|||
discoverable: Suggest account to others
|
||||
discoverable_local: Disallow suggesting account on other servers
|
||||
display_name: Display name
|
||||
domain: Domain
|
||||
email: E-mail address
|
||||
expires_in: Expire after
|
||||
fields: Extra fields
|
||||
|
|
|
@ -229,6 +229,7 @@ ja:
|
|||
discoverable: ディレクトリに掲載する
|
||||
discoverable_local: 他サーバーのディレクトリに掲載しない
|
||||
display_name: 表示名
|
||||
domain: ドメイン
|
||||
email: メールアドレス
|
||||
expires_in: 有効期限
|
||||
fields: プロフィール補足情報
|
||||
|
|
41
db/migrate/20240401222541_create_specified_domains.rb
Normal file
41
db/migrate/20240401222541_create_specified_domains.rb
Normal 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
|
11
db/schema.rb
11
db/schema.rb
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# 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
|
||||
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
|
||||
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|
|
||||
t.bigint "status_id", null: false
|
||||
t.string "token"
|
||||
|
|
|
@ -97,6 +97,7 @@ namespace :dangerous do
|
|||
20240320231633
|
||||
20240326231854
|
||||
20240327234026
|
||||
20240401222541
|
||||
)
|
||||
# Removed: account_groups
|
||||
target_tables = %w(
|
||||
|
@ -121,6 +122,7 @@ namespace :dangerous do
|
|||
pending_statuses
|
||||
scheduled_expiration_statuses
|
||||
sensitive_words
|
||||
specified_domains
|
||||
status_capability_tokens
|
||||
status_references
|
||||
)
|
||||
|
|
5
spec/fabricators/specified_domain_fabricator.rb
Normal file
5
spec/fabricators/specified_domain_fabricator.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:specified_domain) do
|
||||
domain { sequence(:domain) { |i| "example_#{i}.com" } }
|
||||
end
|
|
@ -13,7 +13,7 @@ RSpec.describe ActivityPub::ProcessAccountService do
|
|||
subject { described_class.new.call('alice', 'example.com', payload) }
|
||||
|
||||
let(:hold_remote_new_accounts) { true }
|
||||
let(:permit_new_account_domains) { nil }
|
||||
let(:permit_domain) { nil }
|
||||
let(:payload) do
|
||||
{
|
||||
id: 'https://foo.test',
|
||||
|
@ -26,7 +26,7 @@ RSpec.describe ActivityPub::ProcessAccountService do
|
|||
|
||||
before do
|
||||
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
|
||||
|
||||
it 'creates pending account in a simple case' do
|
||||
|
@ -37,7 +37,7 @@ RSpec.describe ActivityPub::ProcessAccountService do
|
|||
end
|
||||
|
||||
context 'when is blocked' do
|
||||
let(:permit_new_account_domains) { ['foo.bar'] }
|
||||
let(:permit_domain) { 'foo.bar' }
|
||||
|
||||
it 'creates pending account' do
|
||||
expect(subject).to_not be_nil
|
||||
|
@ -98,7 +98,7 @@ RSpec.describe ActivityPub::ProcessAccountService do
|
|||
end
|
||||
|
||||
context 'when is in whitelist' do
|
||||
let(:permit_new_account_domains) { ['example.com'] }
|
||||
let(:permit_domain) { 'example.com' }
|
||||
|
||||
it 'does not create account' do
|
||||
expect(subject).to_not be_nil
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue