From d6fe0c94ca48a10d579cf5ec321c33c6124b402d Mon Sep 17 00:00:00 2001 From: Takeshi Umeda Date: Thu, 5 Nov 2020 04:45:01 +0900 Subject: Add account sensitized (#14361) * Add account sensitized * Fix i18n normalize * Fix description and spec * Fix spec * Fix wording --- app/controllers/admin/accounts_controller.rb | 7 +++++++ app/controllers/api/v1/admin/accounts_controller.rb | 8 ++++++++ app/helpers/statuses_helper.rb | 8 ++++++++ app/lib/activitypub/activity/create.rb | 2 +- app/models/account.rb | 14 ++++++++++++++ app/models/account_warning.rb | 2 +- app/models/admin/account_action.rb | 9 +++++++++ app/models/admin/action_log_filter.rb | 2 ++ app/policies/account_policy.rb | 8 ++++++++ app/serializers/activitypub/note_serializer.rb | 4 ++++ app/serializers/rest/status_serializer.rb | 8 ++++++++ app/views/admin/accounts/show.html.haml | 7 +++++++ app/views/statuses/_detailed_status.html.haml | 6 +++--- app/views/statuses/_simple_status.html.haml | 6 +++--- 14 files changed, 83 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index b9b75727d..1dd7430e0 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 3af572f25..63cc521ed 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 a51597cf3..adb7918c5 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 f275feefc..c77f237f9 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 59d338f5a..d2112a13d 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 157e6c04d..5efc924d5 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 c4ac09520..11ce737f3 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 0ba7e1609..3a1b67e06 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 1b105e92a..679119075 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 f26fd93a4..6f9e1ca63 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 0109de882..bb6df90b7 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 f0a216f6b..d5978eddd 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 b3e9c44fc..a4dd8534f 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 7408749cc..192192700 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 -- cgit