Add account sensitized (#14361)

* Add account sensitized

* Fix i18n normalize

* Fix description and spec

* Fix spec

* Fix wording
This commit is contained in:
Takeshi Umeda 2020-11-05 04:45:01 +09:00 committed by GitHub
parent f90620b2f3
commit d6fe0c94ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 136 additions and 13 deletions

View file

@ -53,6 +53,13 @@ module Admin
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct) redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct)
end end
def unsensitive
authorize @account, :unsensitive?
@account.unsensitize!
log_action :unsensitive, @account
redirect_to admin_account_path(@account.id)
end
def unsilence def unsilence
authorize @account, :unsilence? authorize @account, :unsilence?
@account.unsilence! @account.unsilence!

View file

@ -22,6 +22,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
active active
pending pending
disabled disabled
sensitized
silenced silenced
suspended suspended
username username
@ -68,6 +69,13 @@ class Api::V1::Admin::AccountsController < Api::BaseController
render json: @account, serializer: REST::Admin::AccountSerializer render json: @account, serializer: REST::Admin::AccountSerializer
end end
def unsensitive
authorize @account, :unsensitive?
@account.unsensitize!
log_action :unsensitive, @account
render json: @account, serializer: REST::Admin::AccountSerializer
end
def unsilence def unsilence
authorize @account, :unsilence? authorize @account, :unsilence?
@account.unsilence! @account.unsilence!

View file

@ -117,6 +117,14 @@ module StatusesHelper
end end
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 private
def simplified_text(text) def simplified_text(text)

View file

@ -111,7 +111,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
created_at: @object['published'], created_at: @object['published'],
override_timestamps: @options[:override_timestamps], override_timestamps: @options[:override_timestamps],
reply: @object['inReplyTo'].present?, reply: @object['inReplyTo'].present?,
sensitive: @object['sensitive'] || false, sensitive: @account.sensitized? || @object['sensitive'] || false,
visibility: visibility_from_audience, visibility: visibility_from_audience,
thread: replied_to_status, thread: replied_to_status,
conversation: conversation_from_uri(@object['conversation']), conversation: conversation_from_uri(@object['conversation']),

View file

@ -50,6 +50,7 @@
# avatar_storage_schema_version :integer # avatar_storage_schema_version :integer
# header_storage_schema_version :integer # header_storage_schema_version :integer
# devices_url :string # devices_url :string
# sensitized_at :datetime
# #
class Account < ApplicationRecord class Account < ApplicationRecord
@ -92,6 +93,7 @@ class Account < ApplicationRecord
scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) } scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) }
scope :silenced, -> { where.not(silenced_at: nil) } scope :silenced, -> { where.not(silenced_at: nil) }
scope :suspended, -> { where.not(suspended_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_suspended, -> { where(suspended_at: nil) }
scope :without_silenced, -> { where(silenced_at: nil) } scope :without_silenced, -> { where(silenced_at: nil) }
scope :recent, -> { reorder(id: :desc) } scope :recent, -> { reorder(id: :desc) }
@ -234,6 +236,18 @@ class Account < ApplicationRecord
end end
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! def memorialize!
update!(memorial: true) update!(memorial: true)
end end

View file

@ -13,7 +13,7 @@
# #
class AccountWarning < ApplicationRecord 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 :account, inverse_of: :account_warnings
belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings

View file

@ -8,6 +8,7 @@ class Admin::AccountAction
TYPES = %w( TYPES = %w(
none none
disable disable
sensitive
silence silence
suspend suspend
).freeze ).freeze
@ -64,6 +65,8 @@ class Admin::AccountAction
case type case type
when 'disable' when 'disable'
handle_disable! handle_disable!
when 'sensitive'
handle_sensitive!
when 'silence' when 'silence'
handle_silence! handle_silence!
when 'suspend' when 'suspend'
@ -109,6 +112,12 @@ class Admin::AccountAction
target_account.user&.disable! target_account.user&.disable!
end end
def handle_sensitive!
authorize(target_account, :sensitive?)
log_action(:sensitive, target_account)
target_account.sensitize!
end
def handle_silence! def handle_silence!
authorize(target_account, :silence?) authorize(target_account, :silence?)
log_action(:silence, target_account) log_action(:silence, target_account)

View file

@ -35,9 +35,11 @@ class Admin::ActionLogFilter
reopen_report: { target_type: 'Report', action: 'reopen' }.freeze, reopen_report: { target_type: 'Report', action: 'reopen' }.freeze,
reset_password_user: { target_type: 'User', action: 'reset_password' }.freeze, reset_password_user: { target_type: 'User', action: 'reset_password' }.freeze,
resolve_report: { target_type: 'Report', action: 'resolve' }.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, silence_account: { target_type: 'Account', action: 'silence' }.freeze,
suspend_account: { target_type: 'Account', action: 'suspend' }.freeze, suspend_account: { target_type: 'Account', action: 'suspend' }.freeze,
unassigned_report: { target_type: 'Report', action: 'unassigned' }.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, unsilence_account: { target_type: 'Account', action: 'unsilence' }.freeze,
unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze, unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze,
update_announcement: { target_type: 'Announcement', action: 'update' }.freeze, update_announcement: { target_type: 'Announcement', action: 'update' }.freeze,

View file

@ -25,6 +25,14 @@ class AccountPolicy < ApplicationPolicy
staff? staff?
end end
def sensitive?
staff? && !record.user&.staff?
end
def unsensitive?
staff?
end
def silence? def silence?
staff? && !record.user&.staff? staff? && !record.user&.staff?
end end

View file

@ -95,6 +95,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
ActivityPub::TagManager.instance.cc(object) ActivityPub::TagManager.instance.cc(object)
end end
def sensitive
object.account.sensitized? || object.sensitive
end
def virtual_tags def virtual_tags
object.active_mentions.to_a.sort_by(&:id) + object.tags + object.emojis object.active_mentions.to_a.sort_by(&:id) + object.tags + object.emojis
end end

View file

@ -58,6 +58,14 @@ class REST::StatusSerializer < ActiveModel::Serializer
end end
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 def uri
ActivityPub::TagManager.instance.uri_for(object) ActivityPub::TagManager.instance.uri_for(object)
end end

View file

@ -69,6 +69,8 @@
= t('admin.accounts.confirming') = t('admin.accounts.confirming')
- elsif @account.local? && !@account.user_approved? - elsif @account.local? && !@account.user_approved?
= t('admin.accounts.pending') = t('admin.accounts.pending')
- elsif @account.sensitized?
= t('admin.accounts.sensitive')
- else - else
= t('admin.accounts.no_limits_imposed') = t('admin.accounts.no_limits_imposed')
.dashboard__counters__label= t 'admin.accounts.login_status' .dashboard__counters__label= t 'admin.accounts.login_status'
@ -192,6 +194,11 @@
- else - else
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable'), class: 'button' if can?(:disable, @account.user) = 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? - if @account.silenced?
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account) = 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? - elsif !@account.local? || @account.user_approved?

View file

@ -29,17 +29,17 @@
- if !status.media_attachments.empty? - if !status.media_attachments.empty?
- if status.media_attachments.first.video? - if status.media_attachments.first.video?
- video = status.media_attachments.first - 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 } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- elsif status.media_attachments.first.audio? - elsif status.media_attachments.first.audio?
- audio = status.media_attachments.first - 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 = 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 } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- else - 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 } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- elsif status.preview_card - 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 .detailed-status__meta
%data.dt-published{ value: status.created_at.to_time.iso8601 } %data.dt-published{ value: status.created_at.to_time.iso8601 }

View file

@ -35,17 +35,17 @@
- if !status.media_attachments.empty? - if !status.media_attachments.empty?
- if status.media_attachments.first.video? - if status.media_attachments.first.video?
- video = status.media_attachments.first - 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 } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- elsif status.media_attachments.first.audio? - elsif status.media_attachments.first.audio?
- audio = status.media_attachments.first - 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 = 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 } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- else - 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 } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- elsif status.preview_card - 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 - 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 = link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__content__read-more-button', target: stream_link_target, rel: 'noopener noreferrer' do

View file

@ -188,6 +188,8 @@ en:
search: Search search: Search
search_same_email_domain: Other users with the same e-mail domain search_same_email_domain: Other users with the same e-mail domain
search_same_ip: Other users with the same IP search_same_ip: Other users with the same IP
sensitive: Sensitive
sensitized: marked as sensitive
shared_inbox_url: Shared inbox URL shared_inbox_url: Shared inbox URL
show: show:
created_reports: Made reports created_reports: Made reports
@ -202,6 +204,7 @@ en:
time_in_queue: Waiting in queue %{time} time_in_queue: Waiting in queue %{time}
title: Accounts title: Accounts
unconfirmed_email: Unconfirmed email unconfirmed_email: Unconfirmed email
undo_sensitized: Undo sensitive
undo_silenced: Undo silence undo_silenced: Undo silence
undo_suspension: Undo suspension undo_suspension: Undo suspension
unsilenced_msg: Successfully unlimited %{username}'s account unsilenced_msg: Successfully unlimited %{username}'s account
@ -243,9 +246,11 @@ en:
reopen_report: Reopen Report reopen_report: Reopen Report
reset_password_user: Reset Password reset_password_user: Reset Password
resolve_report: Resolve Report resolve_report: Resolve Report
sensitive_account: Mark the media in your account as sensitive
silence_account: Silence Account silence_account: Silence Account
suspend_account: Suspend Account suspend_account: Suspend Account
unassigned_report: Unassign Report unassigned_report: Unassign Report
unsensitive_account: Unmark the media in your account as sensitive
unsilence_account: Unsilence Account unsilence_account: Unsilence Account
unsuspend_account: Unsuspend Account unsuspend_account: Unsuspend Account
update_announcement: Update Announcement update_announcement: Update Announcement
@ -281,9 +286,11 @@ en:
reopen_report: "%{name} reopened report %{target}" reopen_report: "%{name} reopened report %{target}"
reset_password_user: "%{name} reset password of user %{target}" reset_password_user: "%{name} reset password of user %{target}"
resolve_report: "%{name} resolved report %{target}" resolve_report: "%{name} resolved report %{target}"
sensitive_account: "%{name} marked %{target}'s media as sensitive"
silence_account: "%{name} silenced %{target}'s account" silence_account: "%{name} silenced %{target}'s account"
suspend_account: "%{name} suspended %{target}'s account" suspend_account: "%{name} suspended %{target}'s account"
unassigned_report: "%{name} unassigned report %{target}" unassigned_report: "%{name} unassigned report %{target}"
unsensitive_account: "%{name} unmarked %{target}'s media as sensitive"
unsilence_account: "%{name} unsilenced %{target}'s account" unsilence_account: "%{name} unsilenced %{target}'s account"
unsuspend_account: "%{name} unsuspended %{target}'s account" unsuspend_account: "%{name} unsuspended %{target}'s account"
update_announcement: "%{name} updated announcement %{target}" update_announcement: "%{name} updated announcement %{target}"
@ -1339,6 +1346,7 @@ en:
warning: warning:
explanation: 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. 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. 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. 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}. 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: subject:
disable: Your account %{acct} has been frozen disable: Your account %{acct} has been frozen
none: Warning for %{acct} none: Warning for %{acct}
sensitive: Your account %{acct} posting media has been marked as sensitive
silence: Your account %{acct} has been limited silence: Your account %{acct} has been limited
suspend: Your account %{acct} has been suspended suspend: Your account %{acct} has been suspended
title: title:
disable: Account frozen disable: Account frozen
none: Warning none: Warning
sensitive: Your media has been marked as sensitive
silence: Account limited silence: Account limited
suspend: Account suspended suspend: Account suspended
welcome: welcome:

View file

@ -172,6 +172,8 @@ ja:
search: 検索 search: 検索
search_same_email_domain: 同じドメインのメールアドレスを使用しているユーザー search_same_email_domain: 同じドメインのメールアドレスを使用しているユーザー
search_same_ip: 同じ IP のユーザーを検索 search_same_ip: 同じ IP のユーザーを検索
sensitive: 閲覧注意
sensitized: 閲覧注意済み
shared_inbox_url: Shared inbox URL shared_inbox_url: Shared inbox URL
show: show:
created_reports: このアカウントで作られた通報 created_reports: このアカウントで作られた通報
@ -184,6 +186,7 @@ ja:
time_in_queue: "%{time} 待ち" time_in_queue: "%{time} 待ち"
title: アカウント title: アカウント
unconfirmed_email: 確認待ちのメールアドレス unconfirmed_email: 確認待ちのメールアドレス
undo_sensitized: 閲覧注意から戻す
undo_silenced: サイレンスから戻す undo_silenced: サイレンスから戻す
undo_suspension: 停止から戻す undo_suspension: 停止から戻す
unsubscribe: 購読の解除 unsubscribe: 購読の解除
@ -220,9 +223,11 @@ ja:
reopen_report: 通報を再度開く reopen_report: 通報を再度開く
reset_password_user: パスワードをリセット reset_password_user: パスワードをリセット
resolve_report: 通報を解決済みにする resolve_report: 通報を解決済みにする
sensitive_account: アカウントのメディアを閲覧注意にマーク
silence_account: アカウントをサイレンス silence_account: アカウントをサイレンス
suspend_account: アカウントを停止 suspend_account: アカウントを停止
unassigned_report: 通報の担当を解除 unassigned_report: 通報の担当を解除
unsensitive_account: アカウントのメディアの閲覧注意マークを解除
unsilence_account: アカウントのサイレンスを解除 unsilence_account: アカウントのサイレンスを解除
unsuspend_account: アカウントの停止を解除 unsuspend_account: アカウントの停止を解除
update_announcement: お知らせを更新 update_announcement: お知らせを更新
@ -256,9 +261,11 @@ ja:
reopen_report: "%{name} さんが通報 %{target} を再び開きました" reopen_report: "%{name} さんが通報 %{target} を再び開きました"
reset_password_user: "%{name} さんが %{target} さんのパスワードをリセットしました" reset_password_user: "%{name} さんが %{target} さんのパスワードをリセットしました"
resolve_report: "%{name} さんが通報 %{target} を解決済みにしました" resolve_report: "%{name} さんが通報 %{target} を解決済みにしました"
sensitive_account: "%{name} さんが %{target} さんのメディアを閲覧注意にマークしました"
silence_account: "%{name} さんが %{target} さんをサイレンスにしました" silence_account: "%{name} さんが %{target} さんをサイレンスにしました"
suspend_account: "%{name} さんが %{target} さんを停止しました" suspend_account: "%{name} さんが %{target} さんを停止しました"
unassigned_report: "%{name} さんが通報 %{target} の担当を外しました" unassigned_report: "%{name} さんが通報 %{target} の担当を外しました"
unsensitive_account: "%{name} さんが %{target} さんのメディアの閲覧注意を解除しました"
unsilence_account: "%{name} さんが %{target} さんのサイレンスを解除しました" unsilence_account: "%{name} さんが %{target} さんのサイレンスを解除しました"
unsuspend_account: "%{name} さんが %{target} さんの停止を解除しました" unsuspend_account: "%{name} さんが %{target} さんの停止を解除しました"
update_announcement: "%{name} さんがお知らせ %{target} を更新しました" update_announcement: "%{name} さんがお知らせ %{target} を更新しました"
@ -1271,6 +1278,7 @@ ja:
warning: warning:
explanation: explanation:
disable: アカウントが凍結されている間、データはそのまま残りますが、凍結が解除されるまでは何の操作もできません。 disable: アカウントが凍結されている間、データはそのまま残りますが、凍結が解除されるまでは何の操作もできません。
sensitive: あなたのアップロードしたメディアファイルとリンク先のメディアは、閲覧注意として扱われます。
silence: あなたのアカウントは制限されていますが、あなたをフォローしているユーザーのみ、このサーバー上の投稿を見ることができます。そしてあなたは様々な公開リストから除外されるかもしれません。ただし、他のユーザーは手動であなたをフォローすることができます。 silence: あなたのアカウントは制限されていますが、あなたをフォローしているユーザーのみ、このサーバー上の投稿を見ることができます。そしてあなたは様々な公開リストから除外されるかもしれません。ただし、他のユーザーは手動であなたをフォローすることができます。
suspend: あなたのアカウントは停止されています。あなたの投稿とアップロードされたメディアファイルは、このサーバーとあなたのフォロワーが参加していたサーバーから完全に削除されました。 suspend: あなたのアカウントは停止されています。あなたの投稿とアップロードされたメディアファイルは、このサーバーとあなたのフォロワーが参加していたサーバーから完全に削除されました。
get_in_touch: このメールに返信することで %{instance} のスタッフと連絡を取ることができます。 get_in_touch: このメールに返信することで %{instance} のスタッフと連絡を取ることができます。
@ -1279,11 +1287,13 @@ ja:
subject: subject:
disable: あなたのアカウント %{acct} は凍結されました disable: あなたのアカウント %{acct} は凍結されました
none: "%{acct} に対する警告" none: "%{acct} に対する警告"
sensitive: あなたのアカウント %{acct} の投稿メディアは閲覧注意とマークされました
silence: あなたのアカウント %{acct} はサイレンスにされました silence: あなたのアカウント %{acct} はサイレンスにされました
suspend: あなたのアカウント %{acct} は停止されました suspend: あなたのアカウント %{acct} は停止されました
title: title:
disable: アカウントが凍結されました disable: アカウントが凍結されました
none: 警告 none: 警告
sensitive: あなたのメディアが閲覧注意とマークされました
silence: アカウントがサイレンスにされました silence: アカウントがサイレンスにされました
suspend: アカウントが停止されました suspend: アカウントが停止されました
welcome: welcome:

View file

@ -100,6 +100,7 @@ en:
types: types:
disable: Freeze disable: Freeze
none: Send a warning none: Send a warning
sensitive: Sensitive
silence: Limit silence: Limit
suspend: Suspend suspend: Suspend
warning_preset_id: Use a warning preset warning_preset_id: Use a warning preset

View file

@ -91,6 +91,7 @@ ja:
types: types:
disable: ログインを無効化 disable: ログインを無効化
none: 何もしない none: 何もしない
sensitive: 閲覧注意
silence: サイレンス silence: サイレンス
suspend: 停止しアカウントのデータを恒久的に削除する suspend: 停止しアカウントのデータを恒久的に削除する
warning_preset_id: プリセット警告文を使用 warning_preset_id: プリセット警告文を使用

View file

@ -236,6 +236,7 @@ Rails.application.routes.draw do
resources :accounts, only: [:index, :show, :destroy] do resources :accounts, only: [:index, :show, :destroy] do
member do member do
post :enable post :enable
post :unsensitive
post :unsilence post :unsilence
post :unsuspend post :unsuspend
post :redownload post :redownload
@ -476,6 +477,7 @@ Rails.application.routes.draw do
resources :accounts, only: [:index, :show, :destroy] do resources :accounts, only: [:index, :show, :destroy] do
member do member do
post :enable post :enable
post :unsensitive
post :unsilence post :unsilence
post :unsuspend post :unsuspend
post :approve post :approve

View file

@ -0,0 +1,5 @@
class AddSensitizedToAccounts < ActiveRecord::Migration[5.2]
def change
add_column :accounts, :sensitized_at, :datetime
end
end

View file

@ -189,6 +189,7 @@ ActiveRecord::Schema.define(version: 2020_10_08_220312) do
t.integer "avatar_storage_schema_version" t.integer "avatar_storage_schema_version"
t.integer "header_storage_schema_version" t.integer "header_storage_schema_version"
t.string "devices_url" 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 "(((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 "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" t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id"

View file

@ -127,6 +127,24 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end end
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 describe 'POST #unsilence' do
before do before do
account.touch(:silenced_at) account.touch(:silenced_at)

View file

@ -115,16 +115,16 @@ RSpec.describe Admin::AccountAction, type: :model do
context 'account.local?' do context 'account.local?' do
let(:account) { Fabricate(:account, domain: nil) } let(:account) { Fabricate(:account, domain: nil) }
it 'returns ["none", "disable", "silence", "suspend"]' do it 'returns ["none", "disable", "sensitive", "silence", "suspend"]' do
expect(subject).to eq %w(none disable silence suspend) expect(subject).to eq %w(none disable sensitive silence suspend)
end end
end end
context '!account.local?' do context '!account.local?' do
let(:account) { Fabricate(:account, domain: 'hoge.com') } let(:account) { Fabricate(:account, domain: 'hoge.com') }
it 'returns ["silence", "suspend"]' do it 'returns ["sensitive", "silence", "suspend"]' do
expect(subject).to eq %w(silence suspend) expect(subject).to eq %w(sensitive silence suspend)
end end
end end
end end

View file

@ -8,7 +8,7 @@ RSpec.describe AccountPolicy do
let(:admin) { Fabricate(:user, admin: true).account } let(:admin) { Fabricate(:user, admin: true).account }
let(:john) { Fabricate(:user).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 context 'staff' do
it 'permits' do it 'permits' do
expect(subject).to permit(admin) expect(subject).to permit(admin)