diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index b9b75727dd9d21a14215a9508562e17d662db6f1..1dd7430e098295c88e0d5282dcd7bbe114f63aff 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -53,6 +53,13 @@ module Admin redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct) end + def unsensitive + authorize @account, :unsensitive? + @account.unsensitize! + log_action :unsensitive, @account + redirect_to admin_account_path(@account.id) + end + def unsilence authorize @account, :unsilence? @account.unsilence! diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb index 3af572f25ef1f8593ba19c92022b97985bb89a76..63cc521ed0119912d5f6b9149bb03a0b2b5c2470 100644 --- a/app/controllers/api/v1/admin/accounts_controller.rb +++ b/app/controllers/api/v1/admin/accounts_controller.rb @@ -22,6 +22,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController active pending disabled + sensitized silenced suspended username @@ -68,6 +69,13 @@ class Api::V1::Admin::AccountsController < Api::BaseController render json: @account, serializer: REST::Admin::AccountSerializer end + def unsensitive + authorize @account, :unsensitive? + @account.unsensitize! + log_action :unsensitive, @account + render json: @account, serializer: REST::Admin::AccountSerializer + end + def unsilence authorize @account, :unsilence? @account.unsilence! diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb index a51597cf353e013c159ed61c0728c9d017f12150..adb7918c53fd14b867d5b958744f0c4173f17cc3 100644 --- a/app/helpers/statuses_helper.rb +++ b/app/helpers/statuses_helper.rb @@ -117,6 +117,14 @@ module StatusesHelper end end + def sensitized?(status, account) + if !account.nil? && account.id == status.account_id + status.sensitive + else + status.account.sensitized? || status.sensitive + end + end + private def simplified_text(text) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index f275feefc500e8af6c88944b813c701228440778..c77f237f9a77fac148384a5e368dd0968dbf5c2c 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -111,7 +111,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity created_at: @object['published'], override_timestamps: @options[:override_timestamps], reply: @object['inReplyTo'].present?, - sensitive: @object['sensitive'] || false, + sensitive: @account.sensitized? || @object['sensitive'] || false, visibility: visibility_from_audience, thread: replied_to_status, conversation: conversation_from_uri(@object['conversation']), diff --git a/app/models/account.rb b/app/models/account.rb index 59d338f5af15e0a8314ba8394f463efe8f4a43a2..d2112a13df63598c1ea4bd8a51db782a042f8c7c 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -50,6 +50,7 @@ # avatar_storage_schema_version :integer # header_storage_schema_version :integer # devices_url :string +# sensitized_at :datetime # class Account < ApplicationRecord @@ -92,6 +93,7 @@ class Account < ApplicationRecord scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) } scope :silenced, -> { where.not(silenced_at: nil) } scope :suspended, -> { where.not(suspended_at: nil) } + scope :sensitized, -> { where.not(sensitized_at: nil) } scope :without_suspended, -> { where(suspended_at: nil) } scope :without_silenced, -> { where(silenced_at: nil) } scope :recent, -> { reorder(id: :desc) } @@ -234,6 +236,18 @@ class Account < ApplicationRecord end end + def sensitized? + sensitized_at.present? + end + + def sensitize!(date = Time.now.utc) + update!(sensitized_at: date) + end + + def unsensitize! + update!(sensitized_at: nil) + end + def memorialize! update!(memorial: true) end diff --git a/app/models/account_warning.rb b/app/models/account_warning.rb index 157e6c04d1eb39d41872f3c07f1054179c6d66af..5efc924d5f7ae36a7fb038ff4c2fbfe06a157416 100644 --- a/app/models/account_warning.rb +++ b/app/models/account_warning.rb @@ -13,7 +13,7 @@ # class AccountWarning < ApplicationRecord - enum action: %i(none disable silence suspend), _suffix: :action + enum action: %i(none disable sensitive silence suspend), _suffix: :action belongs_to :account, inverse_of: :account_warnings belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb index c4ac09520ef27556247eeb0681a8e718fc9bb889..11ce737f3e02d6e4b0550063db5c22eb97d723b8 100644 --- a/app/models/admin/account_action.rb +++ b/app/models/admin/account_action.rb @@ -8,6 +8,7 @@ class Admin::AccountAction TYPES = %w( none disable + sensitive silence suspend ).freeze @@ -64,6 +65,8 @@ class Admin::AccountAction case type when 'disable' handle_disable! + when 'sensitive' + handle_sensitive! when 'silence' handle_silence! when 'suspend' @@ -109,6 +112,12 @@ class Admin::AccountAction target_account.user&.disable! end + def handle_sensitive! + authorize(target_account, :sensitive?) + log_action(:sensitive, target_account) + target_account.sensitize! + end + def handle_silence! authorize(target_account, :silence?) log_action(:silence, target_account) diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb index 0ba7e1609449eb9ccf433a4775120e24fb61eb88..3a1b67e06752c7c5f1235673fb04c7972a6e04a5 100644 --- a/app/models/admin/action_log_filter.rb +++ b/app/models/admin/action_log_filter.rb @@ -35,9 +35,11 @@ class Admin::ActionLogFilter reopen_report: { target_type: 'Report', action: 'reopen' }.freeze, reset_password_user: { target_type: 'User', action: 'reset_password' }.freeze, resolve_report: { target_type: 'Report', action: 'resolve' }.freeze, + sensitive_account: { target_type: 'Account', action: 'sensitive' }.freeze, silence_account: { target_type: 'Account', action: 'silence' }.freeze, suspend_account: { target_type: 'Account', action: 'suspend' }.freeze, unassigned_report: { target_type: 'Report', action: 'unassigned' }.freeze, + unsensitive_account: { target_type: 'Account', action: 'unsensitive' }.freeze, unsilence_account: { target_type: 'Account', action: 'unsilence' }.freeze, unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze, update_announcement: { target_type: 'Announcement', action: 'update' }.freeze, diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb index 1b105e92aa8e95097ed3673a2913ba99a993a1f3..679119075e15f54dac12686376211f16966ff5a7 100644 --- a/app/policies/account_policy.rb +++ b/app/policies/account_policy.rb @@ -25,6 +25,14 @@ class AccountPolicy < ApplicationPolicy staff? end + def sensitive? + staff? && !record.user&.staff? + end + + def unsensitive? + staff? + end + def silence? staff? && !record.user&.staff? end diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index f26fd93a424f1ecb30f10f0e11ebbe880080b528..6f9e1ca639183282d9bfb0daa5a3365da1b339f7 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -95,6 +95,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer ActivityPub::TagManager.instance.cc(object) end + def sensitive + object.account.sensitized? || object.sensitive + end + def virtual_tags object.active_mentions.to_a.sort_by(&:id) + object.tags + object.emojis end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 0109de882bdc0021fdbeb9b9c86bd3b41981d602..bb6df90b7a61f53342506f92b7dd945177e940e2 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -58,6 +58,14 @@ class REST::StatusSerializer < ActiveModel::Serializer end end + def sensitive + if current_user? && current_user.account_id == object.account_id + object.sensitive + else + object.account.sensitized? || object.sensitive + end + end + def uri ActivityPub::TagManager.instance.uri_for(object) end diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index f0a216f6b8f622ed7be2171caf3371ff3e8acc18..d5978eddd64a7a2ea5fa07786edb88e55e02574e 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -69,6 +69,8 @@ = t('admin.accounts.confirming') - elsif @account.local? && !@account.user_approved? = t('admin.accounts.pending') + - elsif @account.sensitized? + = t('admin.accounts.sensitive') - else = t('admin.accounts.no_limits_imposed') .dashboard__counters__label= t 'admin.accounts.login_status' @@ -192,6 +194,11 @@ - else = link_to t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable'), class: 'button' if can?(:disable, @account.user) + - if @account.sensitized? + = link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsensitive, @account) + - elsif !@account.local? || @account.user_approved? + = link_to t('admin.accounts.sensitive'), new_admin_account_action_path(@account.id, type: 'sensitive'), class: 'button' if can?(:sensitive, @account) + - if @account.silenced? = link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account) - elsif !@account.local? || @account.user_approved? diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index b3e9c44fc8bdb3b7349398a4c4fb3ddf07087eba..a4dd8534fa56361ec9785c24cc2de808bdbb0aec 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -29,17 +29,17 @@ - if !status.media_attachments.empty? - if status.media_attachments.first.video? - video = status.media_attachments.first - = react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: status.sensitive?, width: 670, height: 380, detailed: true, inline: true, alt: video.description do + = react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: sensitized?(status, current_account), width: 670, height: 380, detailed: true, inline: true, alt: video.description do = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } - elsif status.media_attachments.first.audio? - audio = status.media_attachments.first = react_component :audio, src: full_asset_url(audio.file.url(:original)), poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url), backgroundColor: audio.file.meta.dig('colors', 'background'), foregroundColor: audio.file.meta.dig('colors', 'foreground'), accentColor: audio.file.meta.dig('colors', 'accent'), width: 670, height: 380, alt: audio.description, duration: audio.file.meta.dig('original', 'duration') do = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } - else - = react_component :media_gallery, height: 380, sensitive: status.sensitive?, standalone: true, autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do + = react_component :media_gallery, height: 380, sensitive: sensitized?(status, current_account), standalone: true, autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } - elsif status.preview_card - = react_component :card, sensitive: status.sensitive?, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json + = react_component :card, sensitive: sensitized?(status, current_account), 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json .detailed-status__meta %data.dt-published{ value: status.created_at.to_time.iso8601 } diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml index 7408749cca35ee8dab055a13b022435757f3edb0..1921927005f77e5ec06f25a71dad8df5cc2fc544 100644 --- a/app/views/statuses/_simple_status.html.haml +++ b/app/views/statuses/_simple_status.html.haml @@ -35,17 +35,17 @@ - if !status.media_attachments.empty? - if status.media_attachments.first.video? - video = status.media_attachments.first - = react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: status.sensitive?, width: 610, height: 343, inline: true, alt: video.description do + = react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: sensitized?(status, current_account), width: 610, height: 343, inline: true, alt: video.description do = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } - elsif status.media_attachments.first.audio? - audio = status.media_attachments.first = react_component :audio, src: full_asset_url(audio.file.url(:original)), poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url), backgroundColor: audio.file.meta.dig('colors', 'background'), foregroundColor: audio.file.meta.dig('colors', 'foreground'), accentColor: audio.file.meta.dig('colors', 'accent'), width: 610, height: 343, alt: audio.description, duration: audio.file.meta.dig('original', 'duration') do = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } - else - = react_component :media_gallery, height: 343, sensitive: status.sensitive?, autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do + = react_component :media_gallery, height: 343, sensitive: sensitized?(status, current_account), autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } - elsif status.preview_card - = react_component :card, sensitive: status.sensitive?, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json + = react_component :card, sensitive: sensitized?(status, current_account), 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json - if !status.in_reply_to_id.nil? && status.in_reply_to_account_id == status.account.id = link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__content__read-more-button', target: stream_link_target, rel: 'noopener noreferrer' do diff --git a/config/locales/en.yml b/config/locales/en.yml index 084006a2af1936aee54d0777bc15b77991c48a90..c962bc53202c45cd099c02e6cf644595485c8419 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -188,6 +188,8 @@ en: search: Search search_same_email_domain: Other users with the same e-mail domain search_same_ip: Other users with the same IP + sensitive: Sensitive + sensitized: marked as sensitive shared_inbox_url: Shared inbox URL show: created_reports: Made reports @@ -202,6 +204,7 @@ en: time_in_queue: Waiting in queue %{time} title: Accounts unconfirmed_email: Unconfirmed email + undo_sensitized: Undo sensitive undo_silenced: Undo silence undo_suspension: Undo suspension unsilenced_msg: Successfully unlimited %{username}'s account @@ -243,9 +246,11 @@ en: reopen_report: Reopen Report reset_password_user: Reset Password resolve_report: Resolve Report + sensitive_account: Mark the media in your account as sensitive silence_account: Silence Account suspend_account: Suspend Account unassigned_report: Unassign Report + unsensitive_account: Unmark the media in your account as sensitive unsilence_account: Unsilence Account unsuspend_account: Unsuspend Account update_announcement: Update Announcement @@ -281,9 +286,11 @@ en: reopen_report: "%{name} reopened report %{target}" reset_password_user: "%{name} reset password of user %{target}" resolve_report: "%{name} resolved report %{target}" + sensitive_account: "%{name} marked %{target}'s media as sensitive" silence_account: "%{name} silenced %{target}'s account" suspend_account: "%{name} suspended %{target}'s account" unassigned_report: "%{name} unassigned report %{target}" + unsensitive_account: "%{name} unmarked %{target}'s media as sensitive" unsilence_account: "%{name} unsilenced %{target}'s account" unsuspend_account: "%{name} unsuspended %{target}'s account" update_announcement: "%{name} updated announcement %{target}" @@ -1339,6 +1346,7 @@ en: warning: explanation: disable: You can no longer login to your account or use it in any other way, but your profile and other data remains intact. + sensitive: Your uploaded media files and linked media will be treated as sensitive. silence: You can still use your account but only people who are already following you will see your toots on this server, and you may be excluded from various public listings. However, others may still manually follow you. suspend: You can no longer use your account, and your profile and other data are no longer accessible. You can still login to request a backup of your data until the data is fully removed, but we will retain some data to prevent you from evading the suspension. get_in_touch: You can reply to this e-mail to get in touch with the staff of %{instance}. @@ -1347,11 +1355,13 @@ en: subject: disable: Your account %{acct} has been frozen none: Warning for %{acct} + sensitive: Your account %{acct} posting media has been marked as sensitive silence: Your account %{acct} has been limited suspend: Your account %{acct} has been suspended title: disable: Account frozen none: Warning + sensitive: Your media has been marked as sensitive silence: Account limited suspend: Account suspended welcome: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index fb6255546776a958d6a417f58dcd95b20f5f3cec..fd9ec9427becde8bc39dd4582b47f8579709db7d 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -172,6 +172,8 @@ ja: search: 検索 search_same_email_domain: åŒã˜ãƒ‰ãƒ¡ã‚¤ãƒ³ã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’使用ã—ã¦ã„るユーザー search_same_ip: åŒã˜ IP ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’検索 + sensitive: é–²è¦§æ³¨æ„ + sensitized: 閲覧注æ„済㿠shared_inbox_url: Shared inbox URL show: created_reports: ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ä½œã‚‰ã‚ŒãŸé€šå ± @@ -184,6 +186,7 @@ ja: time_in_queue: "%{time} å¾…ã¡" title: アカウント unconfirmed_email: 確èªå¾…ã¡ã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ + undo_sensitized: 閲覧注æ„ã‹ã‚‰æˆ»ã™ undo_silenced: サイレンスã‹ã‚‰æˆ»ã™ undo_suspension: åœæ¢ã‹ã‚‰æˆ»ã™ unsubscribe: è³¼èªã®è§£é™¤ @@ -220,9 +223,11 @@ ja: reopen_report: é€šå ±ã‚’å†åº¦é–‹ã reset_password_user: パスワードをリセット resolve_report: é€šå ±ã‚’è§£æ±ºæ¸ˆã¿ã«ã™ã‚‹ + sensitive_account: アカウントã®ãƒ¡ãƒ‡ã‚£ã‚¢ã‚’閲覧注æ„ã«ãƒžãƒ¼ã‚¯ silence_account: アカウントをサイレンス suspend_account: アカウントをåœæ¢ unassigned_report: é€šå ±ã®æ‹…当を解除 + unsensitive_account: アカウントã®ãƒ¡ãƒ‡ã‚£ã‚¢ã®é–²è¦§æ³¨æ„マークを解除 unsilence_account: アカウントã®ã‚µã‚¤ãƒ¬ãƒ³ã‚¹ã‚’解除 unsuspend_account: アカウントã®åœæ¢ã‚’解除 update_announcement: ãŠçŸ¥ã‚‰ã›ã‚’æ›´æ–° @@ -256,9 +261,11 @@ ja: reopen_report: "%{name} ã•ã‚“ãŒé€šå ± %{target} ã‚’å†ã³é–‹ãã¾ã—ãŸ" reset_password_user: "%{name} ã•ã‚“㌠%{target} ã•ã‚“ã®ãƒ‘スワードをリセットã—ã¾ã—ãŸ" resolve_report: "%{name} ã•ã‚“ãŒé€šå ± %{target} を解決済ã¿ã«ã—ã¾ã—ãŸ" + sensitive_account: "%{name} ã•ã‚“㌠%{target} ã•ã‚“ã®ãƒ¡ãƒ‡ã‚£ã‚¢ã‚’閲覧注æ„ã«ãƒžãƒ¼ã‚¯ã—ã¾ã—ãŸ" silence_account: "%{name} ã•ã‚“㌠%{target} ã•ã‚“をサイレンスã«ã—ã¾ã—ãŸ" suspend_account: "%{name} ã•ã‚“㌠%{target} ã•ã‚“ã‚’åœæ¢ã—ã¾ã—ãŸ" unassigned_report: "%{name} ã•ã‚“ãŒé€šå ± %{target} ã®æ‹…当を外ã—ã¾ã—ãŸ" + unsensitive_account: "%{name} ã•ã‚“㌠%{target} ã•ã‚“ã®ãƒ¡ãƒ‡ã‚£ã‚¢ã®é–²è¦§æ³¨æ„を解除ã—ã¾ã—ãŸ" unsilence_account: "%{name} ã•ã‚“㌠%{target} ã•ã‚“ã®ã‚µã‚¤ãƒ¬ãƒ³ã‚¹ã‚’解除ã—ã¾ã—ãŸ" unsuspend_account: "%{name} ã•ã‚“㌠%{target} ã•ã‚“ã®åœæ¢ã‚’解除ã—ã¾ã—ãŸ" update_announcement: "%{name} ã•ã‚“ãŒãŠçŸ¥ã‚‰ã› %{target} ã‚’æ›´æ–°ã—ã¾ã—ãŸ" @@ -1271,6 +1278,7 @@ ja: warning: explanation: disable: アカウントãŒå‡çµã•ã‚Œã¦ã„ã‚‹é–“ã€ãƒ‡ãƒ¼ã‚¿ã¯ãã®ã¾ã¾æ®‹ã‚Šã¾ã™ãŒã€å‡çµãŒè§£é™¤ã•ã‚Œã‚‹ã¾ã§ã¯ä½•ã®æ“作もã§ãã¾ã›ã‚“。 + sensitive: ã‚ãªãŸã®ã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ã—ãŸãƒ¡ãƒ‡ã‚£ã‚¢ãƒ•ã‚¡ã‚¤ãƒ«ã¨ãƒªãƒ³ã‚¯å…ˆã®ãƒ¡ãƒ‡ã‚£ã‚¢ã¯ã€é–²è¦§æ³¨æ„ã¨ã—ã¦æ‰±ã‚ã‚Œã¾ã™ã€‚ silence: ã‚ãªãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯åˆ¶é™ã•ã‚Œã¦ã„ã¾ã™ãŒã€ã‚ãªãŸã‚’フォãƒãƒ¼ã—ã¦ã„るユーザーã®ã¿ã€ã“ã®ã‚µãƒ¼ãƒãƒ¼ä¸Šã®æŠ•ç¨¿ã‚’見るã“ã¨ãŒã§ãã¾ã™ã€‚ãã—ã¦ã‚ãªãŸã¯æ§˜ã€…ãªå…¬é–‹ãƒªã‚¹ãƒˆã‹ã‚‰é™¤å¤–ã•ã‚Œã‚‹ã‹ã‚‚ã—ã‚Œã¾ã›ã‚“。ãŸã ã—ã€ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯æ‰‹å‹•ã§ã‚ãªãŸã‚’フォãƒãƒ¼ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ suspend: ã‚ãªãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯åœæ¢ã•ã‚Œã¦ã„ã¾ã™ã€‚ã‚ãªãŸã®æŠ•ç¨¿ã¨ã‚¢ãƒƒãƒ—ãƒãƒ¼ãƒ‰ã•ã‚ŒãŸãƒ¡ãƒ‡ã‚£ã‚¢ãƒ•ã‚¡ã‚¤ãƒ«ã¯ã€ã“ã®ã‚µãƒ¼ãƒãƒ¼ã¨ã‚ãªãŸã®ãƒ•ã‚©ãƒãƒ¯ãƒ¼ãŒå‚åŠ ã—ã¦ã„ãŸã‚µãƒ¼ãƒãƒ¼ã‹ã‚‰å®Œå…¨ã«å‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚ get_in_touch: ã“ã®ãƒ¡ãƒ¼ãƒ«ã«è¿”ä¿¡ã™ã‚‹ã“ã¨ã§ %{instance} ã®ã‚¹ã‚¿ãƒƒãƒ•ã¨é€£çµ¡ã‚’å–ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ @@ -1279,11 +1287,13 @@ ja: subject: disable: ã‚ãªãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ %{acct} ã¯å‡çµã•ã‚Œã¾ã—㟠none: "%{acct} ã«å¯¾ã™ã‚‹è¦å‘Š" + sensitive: ã‚ãªãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ %{acct} ã®æŠ•ç¨¿ãƒ¡ãƒ‡ã‚£ã‚¢ã¯é–²è¦§æ³¨æ„ã¨ãƒžãƒ¼ã‚¯ã•ã‚Œã¾ã—㟠silence: ã‚ãªãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ %{acct} ã¯ã‚µã‚¤ãƒ¬ãƒ³ã‚¹ã«ã•ã‚Œã¾ã—㟠suspend: ã‚ãªãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ %{acct} ã¯åœæ¢ã•ã‚Œã¾ã—㟠title: disable: アカウントãŒå‡çµã•ã‚Œã¾ã—㟠none: è¦å‘Š + sensitive: ã‚ãªãŸã®ãƒ¡ãƒ‡ã‚£ã‚¢ãŒé–²è¦§æ³¨æ„ã¨ãƒžãƒ¼ã‚¯ã•ã‚Œã¾ã—㟠silence: アカウントãŒã‚µã‚¤ãƒ¬ãƒ³ã‚¹ã«ã•ã‚Œã¾ã—㟠suspend: アカウントãŒåœæ¢ã•ã‚Œã¾ã—㟠welcome: diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index b694879535f81ee84f66a16908f84ab92e6e400d..46a4759a8564d589a81e7d61496d9a99237f8a8b 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -100,6 +100,7 @@ en: types: disable: Freeze none: Send a warning + sensitive: Sensitive silence: Limit suspend: Suspend warning_preset_id: Use a warning preset diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index bbc0b5fd77ead61493683745ff5d3c35bd24f964..00f469b870f9e6a103ce7885cdff152cbdbdd8b8 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -91,6 +91,7 @@ ja: types: disable: ãƒã‚°ã‚¤ãƒ³ã‚’無効化 none: 何もã—ãªã„ + sensitive: é–²è¦§æ³¨æ„ silence: サイレンス suspend: åœæ¢ã—アカウントã®ãƒ‡ãƒ¼ã‚¿ã‚’æ’ä¹…çš„ã«å‰Šé™¤ã™ã‚‹ warning_preset_id: プリセットè¦å‘Šæ–‡ã‚’使用 diff --git a/config/routes.rb b/config/routes.rb index e0ef237233370f407d425c442cbb4e3f5136ad82..54c76799ca92cfea9fd6eb72a91b76d9e59d71a2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -236,6 +236,7 @@ Rails.application.routes.draw do resources :accounts, only: [:index, :show, :destroy] do member do post :enable + post :unsensitive post :unsilence post :unsuspend post :redownload @@ -476,6 +477,7 @@ Rails.application.routes.draw do resources :accounts, only: [:index, :show, :destroy] do member do post :enable + post :unsensitive post :unsilence post :unsuspend post :approve diff --git a/db/migrate/20200614002136_add_sensitized_to_accounts.rb b/db/migrate/20200614002136_add_sensitized_to_accounts.rb new file mode 100644 index 0000000000000000000000000000000000000000..bc2dfcb6363bad70f14d57d2b3ad5517cab85477 --- /dev/null +++ b/db/migrate/20200614002136_add_sensitized_to_accounts.rb @@ -0,0 +1,5 @@ +class AddSensitizedToAccounts < ActiveRecord::Migration[5.2] + def change + add_column :accounts, :sensitized_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 262e25b3bab1a5593a30127876c87ca14994d45c..5ff6b6300638ee54812140ad032a649ae5d5b80f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -189,6 +189,7 @@ ActiveRecord::Schema.define(version: 2020_10_08_220312) do t.integer "avatar_storage_schema_version" t.integer "header_storage_schema_version" t.string "devices_url" + t.datetime "sensitized_at" 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), COALESCE(lower((domain)::text), ''::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" diff --git a/spec/controllers/api/v1/admin/accounts_controller_spec.rb b/spec/controllers/api/v1/admin/accounts_controller_spec.rb index f3f9946baac2dbb197586350c93ccc891d0a7710..89cadb222b8a97a862228d720633cd2c5c54a106 100644 --- a/spec/controllers/api/v1/admin/accounts_controller_spec.rb +++ b/spec/controllers/api/v1/admin/accounts_controller_spec.rb @@ -127,6 +127,24 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do end end + describe 'POST #unsensitive' do + before do + account.touch(:sensitized_at) + post :unsensitive, params: { id: account.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', 'user' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'unsensitives account' do + expect(account.reload.sensitized?).to be false + end + end + describe 'POST #unsilence' do before do account.touch(:silenced_at) diff --git a/spec/models/admin/account_action_spec.rb b/spec/models/admin/account_action_spec.rb index 87fc285007867a129ec9a1baae59194cff9194e8..2366b9ca4af23c8dfb1c21b8411ce4217b329423 100644 --- a/spec/models/admin/account_action_spec.rb +++ b/spec/models/admin/account_action_spec.rb @@ -115,16 +115,16 @@ RSpec.describe Admin::AccountAction, type: :model do context 'account.local?' do let(:account) { Fabricate(:account, domain: nil) } - it 'returns ["none", "disable", "silence", "suspend"]' do - expect(subject).to eq %w(none disable silence suspend) + it 'returns ["none", "disable", "sensitive", "silence", "suspend"]' do + expect(subject).to eq %w(none disable sensitive silence suspend) end end context '!account.local?' do let(:account) { Fabricate(:account, domain: 'hoge.com') } - it 'returns ["silence", "suspend"]' do - expect(subject).to eq %w(silence suspend) + it 'returns ["sensitive", "silence", "suspend"]' do + expect(subject).to eq %w(sensitive silence suspend) end end end diff --git a/spec/policies/account_policy_spec.rb b/spec/policies/account_policy_spec.rb index 6648b0888bf6e830379875285f7699de23e4aeaf..d27e9d5b07e161296629ebdfd050d5e18942cfea 100644 --- a/spec/policies/account_policy_spec.rb +++ b/spec/policies/account_policy_spec.rb @@ -8,7 +8,7 @@ RSpec.describe AccountPolicy do let(:admin) { Fabricate(:user, admin: true).account } let(:john) { Fabricate(:user).account } - permissions :index?, :show?, :unsuspend?, :unsilence?, :remove_avatar?, :remove_header? do + permissions :index?, :show?, :unsuspend?, :unsensitive?, :unsilence?, :remove_avatar?, :remove_header? do context 'staff' do it 'permits' do expect(subject).to permit(admin)