Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .haml-lint_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
23 changes: 23 additions & 0 deletions app/controllers/admin/ng_words/white_list_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
41 changes: 11 additions & 30 deletions app/javascript/packs/admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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');
},
);

Rails.delegate(document, '#ng-words-table .add-row-button', 'click', (ev) => {
ev.preventDefault();
addTableRow('ng-words-table');
});
removeTableRow(ev.target, id);
});
};

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');
Expand Down
6 changes: 3 additions & 3 deletions app/models/form/account_batch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ def approve_remote!

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
Expand Down
2 changes: 0 additions & 2 deletions app/models/form/admin_settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -121,7 +120,6 @@ class Form::AdminSettings

STRING_ARRAY_KEYS = %i(
emoji_reaction_disallow_domains
permit_new_account_domains
).freeze

attr_accessor(*KEYS)
Expand Down
78 changes: 78 additions & 0 deletions app/models/specified_domain.rb
Original file line number Diff line number Diff line change
@@ -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
6 changes: 1 addition & 5 deletions app/services/activitypub/process_account_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,7 @@ def set_immediate_attributes!
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?
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
20 changes: 18 additions & 2 deletions app/views/admin/ng_words/white_list/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 3 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion config/locales/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: 正規
Expand All @@ -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ワードとスパム
Expand Down
1 change: 1 addition & 0 deletions config/locales/simple_form.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions config/locales/simple_form.ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ ja:
discoverable: ディレクトリに掲載する
discoverable_local: 他サーバーのディレクトリに掲載しない
display_name: 表示名
domain: ドメイン
email: メールアドレス
expires_in: 有効期限
fields: プロフィール補足情報
Expand Down
41 changes: 41 additions & 0 deletions db/migrate/20240401222541_create_specified_domains.rb
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -1328,6 +1328,15 @@
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"
Expand Down
2 changes: 2 additions & 0 deletions lib/tasks/dangerous.rake
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ namespace :dangerous do
20240320231633
20240326231854
20240327234026
20240401222541
)
# Removed: account_groups
target_tables = %w(
Expand All @@ -121,6 +122,7 @@ namespace :dangerous do
pending_statuses
scheduled_expiration_statuses
sensitive_words
specified_domains
status_capability_tokens
status_references
)
Expand Down
5 changes: 5 additions & 0 deletions spec/fabricators/specified_domain_fabricator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

Fabricator(:specified_domain) do
domain { sequence(:domain) { |i| "example_#{i}.com" } }
end
Loading
Loading