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

Add logging of admin actions #5757

Merged
merged 15 commits into from
Nov 24, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def create

def destroy
authorize @account_moderation_note, :destroy?
@account_moderation_note.destroy
@account_moderation_note.destroy!
redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg')
end

Expand Down
3 changes: 3 additions & 0 deletions app/controllers/admin/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,21 @@ def unsubscribe
def memorialize
authorize @account, :memorialize?
@account.memorialize!
log_action :memorialize, @account
redirect_to admin_account_path(@account.id)
end

def enable
authorize @account.user, :enable?
@account.user.enable!
log_action :enable, @account.user
redirect_to admin_account_path(@account.id)
end

def disable
authorize @account.user, :disable?
@account.user.disable!
log_action :disable, @account.user
redirect_to admin_account_path(@account.id)
end

Expand Down
9 changes: 9 additions & 0 deletions app/controllers/admin/action_logs_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module Admin
class ActionLogsController < BaseController
def index
@action_logs = Admin::ActionLog.page(params[:page])
end
end
end
1 change: 1 addition & 0 deletions app/controllers/admin/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module Admin
class BaseController < ApplicationController
include Authorization
include AccountableConcern

before_action :require_staff!

Expand Down
1 change: 1 addition & 0 deletions app/controllers/admin/confirmations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class ConfirmationsController < BaseController
def create
authorize @user, :confirm?
@user.confirm!
log_action :confirm, @user
redirect_to admin_accounts_path
end

Expand Down
8 changes: 7 additions & 1 deletion app/controllers/admin/custom_emojis_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def create
@custom_emoji = CustomEmoji.new(resource_params)

if @custom_emoji.save
log_action :create, @custom_emoji
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg')
else
render :new
Expand All @@ -30,6 +31,7 @@ def update
authorize @custom_emoji, :update?

if @custom_emoji.update(resource_params)
log_action :update, @custom_emoji
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg')
else
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.update_failed_msg')
Expand All @@ -38,7 +40,8 @@ def update

def destroy
authorize @custom_emoji, :destroy?
@custom_emoji.destroy
@custom_emoji.destroy!
log_action :destroy, @custom_emoji
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
end

Expand All @@ -49,6 +52,7 @@ def copy
emoji.image = @custom_emoji.image

if emoji.save
log_action :create, emoji
flash[:notice] = I18n.t('admin.custom_emojis.copied_msg')
else
flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
Expand All @@ -60,12 +64,14 @@ def copy
def enable
authorize @custom_emoji, :enable?
@custom_emoji.update!(disabled: false)
log_action :enable, @custom_emoji
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg')
end

def disable
authorize @custom_emoji, :disable?
@custom_emoji.update!(disabled: true)
log_action :disable, @custom_emoji
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg')
end

Expand Down
2 changes: 2 additions & 0 deletions app/controllers/admin/domain_blocks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def create

if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id)
log_action :create, @domain_block
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.created_msg')
else
render :new
Expand All @@ -34,6 +35,7 @@ def show
def destroy
authorize @domain_block, :destroy?
UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
log_action :destroy, @domain_block
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg')
end

Expand Down
4 changes: 3 additions & 1 deletion app/controllers/admin/email_domain_blocks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def create
@email_domain_block = EmailDomainBlock.new(resource_params)

if @email_domain_block.save
log_action :create, @email_domain_block
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
else
render :new
Expand All @@ -28,7 +29,8 @@ def create

def destroy
authorize @email_domain_block, :destroy?
@email_domain_block.destroy
@email_domain_block.destroy!
log_action :destroy, @email_domain_block
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg')
end

Expand Down
6 changes: 4 additions & 2 deletions app/controllers/admin/reported_statuses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@ class ReportedStatusesController < BaseController
def create
authorize :status, :update?

@form = Form::StatusBatch.new(form_status_batch_params)
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account))
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save

redirect_to admin_report_path(@report)
end

def update
authorize @status, :update?
@status.update(status_params)
@status.update!(status_params)
log_action :update, @status
redirect_to admin_report_path(@report)
end

def destroy
authorize @status, :destroy?
RemovalWorker.perform_async(@status.id)
log_action :destroy, @status
render json: @status
end

Expand Down
9 changes: 7 additions & 2 deletions app/controllers/admin/reports_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ def update
def process_report
case params[:outcome].to_s
when 'resolve'
@report.update(action_taken_by_current_attributes)
@report.update!(action_taken_by_current_attributes)
log_action :resolve, @report
when 'suspend'
Admin::SuspensionWorker.perform_async(@report.target_account.id)
log_action :resolve, @report
log_action :suspend, @report.target_account
resolve_all_target_account_reports
when 'silence'
@report.target_account.update(silenced: true)
@report.target_account.update!(silenced: true)
log_action :resolve, @report
log_action :silence, @report.target_account
resolve_all_target_account_reports
else
raise ActiveRecord::RecordNotFound
Expand Down
1 change: 1 addition & 0 deletions app/controllers/admin/resets_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class ResetsController < BaseController
def create
authorize @user, :reset_password?
@user.send_reset_password_instructions
log_action :reset_password, @user
redirect_to admin_accounts_path
end

Expand Down
2 changes: 2 additions & 0 deletions app/controllers/admin/roles_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ class RolesController < BaseController
def promote
authorize @user, :promote?
@user.promote!
log_action :promote, @user
redirect_to admin_account_path(@user.account_id)
end

def demote
authorize @user, :demote?
@user.demote!
log_action :demote, @user
redirect_to admin_account_path(@user.account_id)
end

Expand Down
6 changes: 4 additions & 2 deletions app/controllers/admin/silences_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ class SilencesController < BaseController

def create
authorize @account, :silence?
@account.update(silenced: true)
@account.update!(silenced: true)
log_action :silence, @account
redirect_to admin_accounts_path
end

def destroy
authorize @account, :unsilence?
@account.update(silenced: false)
@account.update!(silenced: false)
log_action :unsilence, @account
redirect_to admin_accounts_path
end

Expand Down
6 changes: 4 additions & 2 deletions app/controllers/admin/statuses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,23 @@ def index
def create
authorize :status, :update?

@form = Form::StatusBatch.new(form_status_batch_params)
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account))
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save

redirect_to admin_account_statuses_path(@account.id, current_params)
end

def update
authorize @status, :update?
@status.update(status_params)
@status.update!(status_params)
log_action :update, @status
redirect_to admin_account_statuses_path(@account.id, current_params)
end

def destroy
authorize @status, :destroy?
RemovalWorker.perform_async(@status.id)
log_action :destroy, @status
render json: @status
end

Expand Down
2 changes: 2 additions & 0 deletions app/controllers/admin/suspensions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ class SuspensionsController < BaseController
def create
authorize @account, :suspend?
Admin::SuspensionWorker.perform_async(@account.id)
log_action :suspend, @account
redirect_to admin_accounts_path
end

def destroy
authorize @account, :unsuspend?
@account.unsuspend!
log_action :unsuspend, @account
redirect_to admin_accounts_path
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class TwoFactorAuthenticationsController < BaseController
def destroy
authorize @user, :disable_2fa?
@user.disable_two_factor!
log_action :disable_2fa, @user
redirect_to admin_accounts_path
end

Expand Down
9 changes: 9 additions & 0 deletions app/controllers/concerns/accountable_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module AccountableConcern
extend ActiveSupport::Concern

def log_action(action, target)
Admin::ActionLog.create(account: current_account, action: action, target: target)
end
end
103 changes: 103 additions & 0 deletions app/helpers/admin/action_logs_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# frozen_string_literal: true

module Admin::ActionLogsHelper
def log_target(log)
if log.target
linkable_log_target(log.target)
else
log_target_from_history(log.target_type, log.recorded_changes)
end
end

def linkable_log_target(record)
case record.class.name
when 'Account'
link_to record.acct, admin_account_path(record.id)
when 'User'
link_to record.account.acct, admin_account_path(record.account_id)
when 'CustomEmoji'
record.shortcode
when 'Report'
link_to "##{record.id}", admin_report_path(record)
when 'DomainBlock', 'EmailDomainBlock'
link_to record.domain, "https://#{record.domain}"
when 'Status'
link_to record.account.acct, TagManager.instance.url_for(record)
end
end

def log_target_from_history(type, attributes)
case type
when 'CustomEmoji'
attributes['shortcode']
when 'DomainBlock', 'EmailDomainBlock'
link_to attributes['domain'], "https://#{attributes['domain']}"
when 'Status'
tmp_status = Status.new(attributes)
link_to tmp_status.account.acct, TagManager.instance.url_for(tmp_status)
end
end

def relevant_log_changes(log)
if log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action)
log.recorded_changes.slice('domain')
elsif log.target_type == 'CustomEmoji' && log.action == :update
log.recorded_changes.slice('domain', 'visible_in_picker')
elsif log.target_type == 'User' && [:promote, :demote].include?(log.action)
log.recorded_changes.slice('moderator', 'admin')
elsif log.target_type == 'DomainBlock'
log.recorded_changes.slice('severity', 'reject_media')
elsif log.target_type == 'Status' && log.action == :update
log.recorded_changes.slice('sensitive')
end
end

def log_extra_attributes(hash)
safe_join(hash.to_a.map { |key, value| safe_join([content_tag(:span, key, class: 'diff-key'), '=', log_change(value)]) }, ' ')
end

def log_change(val)
return content_tag(:span, val, class: 'diff-neutral') unless val.is_a?(Array)
safe_join([content_tag(:span, val.first, class: 'diff-old'), content_tag(:span, val.last, class: 'diff-new')], '→')
end

def icon_for_log(log)
case log.target_type
when 'Account', 'User'
'user'
when 'CustomEmoji'
'file'
when 'Report'
'flag'
when 'DomainBlock'
'lock'
when 'EmailDomainBlock'
'envelope'
when 'Status'
'pencil'
end
end

def class_for_log_icon(log)
case log.action
when :enable, :unsuspend, :unsilence, :confirm, :promote, :resolve
'positive'
when :create
opposite_verbs?(log) ? 'negative' : 'positive'
when :update, :reset_password, :disable_2fa, :memorialize
'neutral'
when :demote, :silence, :disable, :suspend
'negative'
when :destroy
opposite_verbs?(log) ? 'positive' : 'negative'
else
''
end
end

private

def opposite_verbs?(log)
%w(DomainBlock EmailDomainBlock).include?(log.target_type)
end
end