From fcc4c9b34a6ab771c9cef6673e817866773e12d0 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 18 Jan 2023 16:20:52 +0100 Subject: Change domain block CSV parsing to be more robust and handle more lists (#21470) * Change domain block CSV parsing to be more robust and handle more lists * Add some tests * Improve domain block import validation and reporting --- .../admin/export_domain_blocks_controller_spec.rb | 34 ++++++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) (limited to 'spec/controllers') diff --git a/spec/controllers/admin/export_domain_blocks_controller_spec.rb b/spec/controllers/admin/export_domain_blocks_controller_spec.rb index 8697e0c21..2766102c8 100644 --- a/spec/controllers/admin/export_domain_blocks_controller_spec.rb +++ b/spec/controllers/admin/export_domain_blocks_controller_spec.rb @@ -9,9 +9,9 @@ RSpec.describe Admin::ExportDomainBlocksController, type: :controller do describe 'GET #export' do it 'renders instances' do - Fabricate(:domain_block, domain: 'bad.domain', severity: 'silence', public_comment: 'bad') - Fabricate(:domain_block, domain: 'worse.domain', severity: 'suspend', reject_media: true, reject_reports: true, public_comment: 'worse', obfuscate: true) - Fabricate(:domain_block, domain: 'reject.media', severity: 'noop', reject_media: true, public_comment: 'reject media') + Fabricate(:domain_block, domain: 'bad.domain', severity: 'silence', public_comment: 'bad server') + Fabricate(:domain_block, domain: 'worse.domain', severity: 'suspend', reject_media: true, reject_reports: true, public_comment: 'worse server', obfuscate: true) + Fabricate(:domain_block, domain: 'reject.media', severity: 'noop', reject_media: true, public_comment: 'reject media and test unicode characters ♥') Fabricate(:domain_block, domain: 'no.op', severity: 'noop', public_comment: 'noop') get :export, params: { format: :csv } @@ -21,10 +21,32 @@ RSpec.describe Admin::ExportDomainBlocksController, type: :controller do end describe 'POST #import' do - it 'blocks imported domains' do - post :import, params: { admin_import: { data: fixture_file_upload('domain_blocks.csv') } } + context 'with complete domain blocks CSV' do + before do + post :import, params: { admin_import: { data: fixture_file_upload('domain_blocks.csv') } } + end - expect(assigns(:domain_blocks).map(&:domain)).to match_array ['bad.domain', 'worse.domain', 'reject.media'] + it 'renders page with expected domain blocks' do + expect(assigns(:domain_blocks).map { |block| [block.domain, block.severity.to_sym] }).to match_array [['bad.domain', :silence], ['worse.domain', :suspend], ['reject.media', :noop]] + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + context 'with a list of only domains' do + before do + post :import, params: { admin_import: { data: fixture_file_upload('domain_blocks_list.txt') } } + end + + it 'renders page with expected domain blocks' do + expect(assigns(:domain_blocks).map { |block| [block.domain, block.severity.to_sym] }).to match_array [['bad.domain', :suspend], ['worse.domain', :suspend], ['reject.media', :suspend]] + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end end end -- cgit From 343e1fe8e9ce94ea4f86d3a3df71f22f5fb2319d Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 18 Jan 2023 16:40:09 +0100 Subject: Add confirmation screen when handling reports (#22375) * Add confirmation screen on moderation actions * Add flash notice when a report has been processed * Refactor tests * Add tests --- .../admin/account_actions_controller.rb | 2 +- .../admin/reports/actions_controller.rb | 17 ++- app/models/admin/status_batch_action.rb | 9 +- app/views/admin/reports/_actions.html.haml | 2 +- app/views/admin/reports/actions/preview.html.haml | 78 +++++++++++++ config/i18n-tasks.yml | 2 + config/locales/en.yml | 21 ++++ config/routes.rb | 6 +- .../admin/reports/actions_controller_spec.rb | 128 ++++++++++++++++++--- 9 files changed, 238 insertions(+), 27 deletions(-) create mode 100644 app/views/admin/reports/actions/preview.html.haml (limited to 'spec/controllers') diff --git a/app/controllers/admin/account_actions_controller.rb b/app/controllers/admin/account_actions_controller.rb index 3f2e28b6a..e89404b60 100644 --- a/app/controllers/admin/account_actions_controller.rb +++ b/app/controllers/admin/account_actions_controller.rb @@ -21,7 +21,7 @@ module Admin account_action.save! if account_action.with_report? - redirect_to admin_reports_path + redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: params[:report_id]) else redirect_to admin_account_path(@account.id) end diff --git a/app/controllers/admin/reports/actions_controller.rb b/app/controllers/admin/reports/actions_controller.rb index 5cb5c744f..554f7906f 100644 --- a/app/controllers/admin/reports/actions_controller.rb +++ b/app/controllers/admin/reports/actions_controller.rb @@ -3,6 +3,11 @@ class Admin::Reports::ActionsController < Admin::BaseController before_action :set_report + def preview + authorize @report, :show? + @moderation_action = action_from_button + end + def create authorize @report, :show? @@ -13,7 +18,8 @@ class Admin::Reports::ActionsController < Admin::BaseController status_ids: @report.status_ids, current_account: current_account, report_id: @report.id, - send_email_notification: !@report.spam? + send_email_notification: !@report.spam?, + text: params[:text] ) status_batch_action.save! @@ -23,13 +29,16 @@ class Admin::Reports::ActionsController < Admin::BaseController report_id: @report.id, target_account: @report.target_account, current_account: current_account, - send_email_notification: !@report.spam? + send_email_notification: !@report.spam?, + text: params[:text] ) account_action.save! + else + return redirect_to admin_report_path(@report), alert: I18n.t('admin.reports.unknown_action_msg', action: action_from_button) end - redirect_to admin_reports_path + redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: @report.id) end private @@ -47,6 +56,8 @@ class Admin::Reports::ActionsController < Admin::BaseController 'silence' elsif params[:suspend] 'suspend' + elsif params[:moderation_action] + params[:moderation_action] end end end diff --git a/app/models/admin/status_batch_action.rb b/app/models/admin/status_batch_action.rb index 39cd7d0eb..b8bdec722 100644 --- a/app/models/admin/status_batch_action.rb +++ b/app/models/admin/status_batch_action.rb @@ -6,7 +6,8 @@ class Admin::StatusBatchAction include Authorization attr_accessor :current_account, :type, - :status_ids, :report_id + :status_ids, :report_id, + :text attr_reader :send_email_notification @@ -57,7 +58,8 @@ class Admin::StatusBatchAction action: :delete_statuses, account: current_account, report: report, - status_ids: status_ids + status_ids: status_ids, + text: text ) statuses.each { |status| Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true) } unless target_account.local? @@ -95,7 +97,8 @@ class Admin::StatusBatchAction action: :mark_statuses_as_sensitive, account: current_account, report: report, - status_ids: status_ids + status_ids: status_ids, + text: text ) UserMailer.warning(target_account.user, @warning).deliver_later! if warnable? diff --git a/app/views/admin/reports/_actions.html.haml b/app/views/admin/reports/_actions.html.haml index 486eb486c..aad441625 100644 --- a/app/views/admin/reports/_actions.html.haml +++ b/app/views/admin/reports/_actions.html.haml @@ -1,4 +1,4 @@ -= form_tag admin_report_actions_path(@report), method: :post do += form_tag preview_admin_report_actions_path(@report), method: :post do .report-actions .report-actions__item .report-actions__item__button diff --git a/app/views/admin/reports/actions/preview.html.haml b/app/views/admin/reports/actions/preview.html.haml new file mode 100644 index 000000000..58745319c --- /dev/null +++ b/app/views/admin/reports/actions/preview.html.haml @@ -0,0 +1,78 @@ +- target_acct = @report.target_account.acct +- warning_action = { 'delete' => 'delete_statuses', 'mark_as_sensitive' => 'mark_statuses_as_sensitive' }.fetch(@moderation_action, @moderation_action) + +- content_for :page_title do + = t('admin.reports.confirm_action', acct: target_acct) + += form_tag admin_report_actions_path(@report), class: 'simple_form', method: :post do + = hidden_field_tag :moderation_action, @moderation_action + + %p.hint= t("admin.reports.summary.action_preambles.#{@moderation_action}_html", acct: target_acct) + %ul.hint + %li.warning-hint= t("admin.reports.summary.actions.#{@moderation_action}_html", acct: target_acct) + - if @moderation_action == 'suspend' + %li.warning-hint= t('admin.reports.summary.delete_data_html', acct: target_acct) + - if %w(silence suspend).include?(@moderation_action) + %li.warning-hint= t('admin.reports.summary.close_reports_html', acct: target_acct) + - else + %li= t('admin.reports.summary.close_report', id: @report.id) + %li= t('admin.reports.summary.record_strike_html', acct: target_acct) + - if @report.target_account.local? && !@report.spam? + %li= t('admin.reports.summary.send_email_html', acct: target_acct) + + %hr.spacer/ + + - if @report.target_account.local? + %p.hint= t('admin.reports.summary.preview_preamble_html', acct: target_acct) + + .strike-card + - unless warning_action == 'none' + %p= t "user_mailer.warning.explanation.#{warning_action}", instance: Rails.configuration.x.local_domain + + .fields-group + = text_area_tag :text, nil, placeholder: t('admin.reports.summary.warning_placeholder') + + - if !@report.other? + %p + %strong= t('user_mailer.warning.reason') + = t("user_mailer.warning.categories.#{@report.category}") + + - if @report.violation? && @report.rule_ids.present? + %ul.strike-card__rules + - @report.rules.each do |rule| + %li + %span.strike-card__rules__text= rule.text + + - if @report.status_ids.present? && !@report.status_ids.empty? + %p + %strong= t('user_mailer.warning.statuses') + + .strike-card__statuses-list + - status_map = @report.statuses.includes(:application, :media_attachments).index_by(&:id) + + - @report.status_ids.each do |status_id| + .strike-card__statuses-list__item + - if (status = status_map[status_id.to_i]) + .one-liner + = link_to short_account_status_url(@report.target_account, status_id), class: 'emojify' do + = one_line_preview(status) + + - status.ordered_media_attachments.each do |media_attachment| + %abbr{ title: media_attachment.description } + = fa_icon 'link' + = media_attachment.file_file_name + .strike-card__statuses-list__item__meta + %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) + - unless status.application.nil? + · + = status.application.name + - else + .one-liner= t('disputes.strikes.status', id: status_id) + .strike-card__statuses-list__item__meta + = t('disputes.strikes.status_removed') + + %hr.spacer/ + + .actions + = link_to t('admin.reports.cancel'), admin_report_path(@report), class: 'button button-tertiary' + = button_tag t('admin.reports.confirm'), name: :confirm, class: 'button', type: :submit diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index c1da42bd8..46dd3124b 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -58,6 +58,8 @@ ignore_unused: - 'errors.429' - 'admin.accounts.roles.*' - 'admin.action_logs.actions.*' + - 'admin.reports.summary.action_preambles.*' + - 'admin.reports.summary.actions.*' - 'admin_mailer.new_appeal.actions.*' - 'statuses.attached.*' - 'move_handler.carry_{mutes,blocks}_over_text' diff --git a/config/locales/en.yml b/config/locales/en.yml index 4143aab04..3de2f2772 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -590,6 +590,7 @@ en: comment: none: None comment_description_html: 'To provide more information, %{name} wrote:' + confirm_action: Confirm moderation action against @%{acct} created_at: Reported delete_and_resolve: Delete posts forwarded: Forwarded @@ -606,6 +607,7 @@ en: placeholder: Describe what actions have been taken, or any other related updates... title: Notes notes_description_html: View and leave notes to other moderators and your future self + processed_msg: 'Report #%{id} successfully processed' quick_actions_description_html: 'Take a quick action or scroll down to see reported content:' remote_user_placeholder: the remote user from %{instance} reopen: Reopen report @@ -618,9 +620,28 @@ en: status: Status statuses: Reported content statuses_description_html: Offending content will be cited in communication with the reported account + summary: + action_preambles: + delete_html: 'You are about to remove some of @%{acct}''s posts. This will:' + mark_as_sensitive_html: 'You are about to mark some of @%{acct}''s posts as sensitive. This will:' + silence_html: 'You are about to limit @%{acct}''s account. This will:' + suspend_html: 'You are about to suspend @%{acct}''s account. This will:' + actions: + delete_html: Remove the offending posts + mark_as_sensitive_html: Mark the offending posts' media as sensitive + silence_html: Severely limit @%{acct}'s reach by making their profile and contents only visible to people already following them or manually looking it profile up + suspend_html: Suspend @%{acct}, making their profile and contents inaccessible and impossible to interact with + close_report: 'Mark report #%{id} as resolved' + close_reports_html: Mark all reports against @%{acct} as resolved + delete_data_html: Delete @%{acct}'s profile and contents 30 days from now unless they get unsuspended in the meantime + preview_preamble_html: "@%{acct} will receive a warning with the following contents:" + record_strike_html: Record a strike against @%{acct} to help you escalate on future violations from this account + send_email_html: Send @%{acct} a warning e-mail + warning_placeholder: Optional additional reasoning for the moderation action. target_origin: Origin of reported account title: Reports unassign: Unassign + unknown_action_msg: 'Unknown action: %{action}' unresolved: Unresolved updated_at: Updated view_profile: View profile diff --git a/config/routes.rb b/config/routes.rb index 98e19667c..0bee2f639 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -310,7 +310,11 @@ Rails.application.routes.draw do end resources :reports, only: [:index, :show] do - resources :actions, only: [:create], controller: 'reports/actions' + resources :actions, only: [:create], controller: 'reports/actions' do + collection do + post :preview + end + end member do post :assign_to_self diff --git a/spec/controllers/admin/reports/actions_controller_spec.rb b/spec/controllers/admin/reports/actions_controller_spec.rb index 6609798dc..9890ac9ce 100644 --- a/spec/controllers/admin/reports/actions_controller_spec.rb +++ b/spec/controllers/admin/reports/actions_controller_spec.rb @@ -4,39 +4,131 @@ describe Admin::Reports::ActionsController do render_views let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:account) { Fabricate(:account) } - let!(:status) { Fabricate(:status, account: account) } - let(:media_attached_status) { Fabricate(:status, account: account) } - let!(:media_attachment) { Fabricate(:media_attachment, account: account, status: media_attached_status) } - let(:media_attached_deleted_status) { Fabricate(:status, account: account, deleted_at: 1.day.ago) } - let!(:media_attachment2) { Fabricate(:media_attachment, account: account, status: media_attached_deleted_status) } - let(:last_media_attached_status) { Fabricate(:status, account: account) } - let!(:last_media_attachment) { Fabricate(:media_attachment, account: account, status: last_media_attached_status) } - let!(:last_status) { Fabricate(:status, account: account) } before do sign_in user, scope: :user end - describe 'POST #create' do - let(:report) { Fabricate(:report, status_ids: status_ids, account: user.account, target_account: account) } - let(:status_ids) { [media_attached_status.id, media_attached_deleted_status.id] } + describe 'POST #preview' do + let(:report) { Fabricate(:report) } before do - post :create, params: { report_id: report.id, action => '' } + post :preview, params: { report_id: report.id, action => '' } + end + + context 'when the action is "suspend"' do + let(:action) { 'suspend' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end end - context 'when action is mark_as_sensitive' do + context 'when the action is "silence"' do + let(:action) { 'silence' } + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + context 'when the action is "delete"' do + let(:action) { 'delete' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + context 'when the action is "mark_as_sensitive"' do let(:action) { 'mark_as_sensitive' } - it 'resolves the report' do - expect(report.reload.action_taken_at).to_not be_nil + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + end + + describe 'POST #create' do + let(:target_account) { Fabricate(:account) } + let(:statuses) { [Fabricate(:status, account: target_account), Fabricate(:status, account: target_account)] } + let!(:media) { Fabricate(:media_attachment, account: target_account, status: statuses[0]) } + let(:report) { Fabricate(:report, target_account: target_account, status_ids: statuses.map(&:id)) } + let(:text) { 'hello' } + + shared_examples 'common behavior' do + it 'closes the report' do + expect { subject }.to change { report.reload.action_taken? }.from(false).to(true) end - it 'marks the non-deleted as sensitive' do - expect(media_attached_status.reload.sensitive).to eq true + it 'creates a strike with the expected text' do + expect { subject }.to change { report.target_account.strikes.count }.by(1) + expect(report.target_account.strikes.last.text).to eq text end + + it 'redirects' do + subject + expect(response).to redirect_to(admin_reports_path) + end + end + + shared_examples 'all action types' do + context 'when the action is "suspend"' do + let(:action) { 'suspend' } + + it_behaves_like 'common behavior' + + it 'suspends the target account' do + expect { subject }.to change { report.target_account.reload.suspended? }.from(false).to(true) + end + end + + context 'when the action is "silence"' do + let(:action) { 'silence' } + + it_behaves_like 'common behavior' + + it 'suspends the target account' do + expect { subject }.to change { report.target_account.reload.silenced? }.from(false).to(true) + end + end + + context 'when the action is "delete"' do + let(:action) { 'delete' } + + it_behaves_like 'common behavior' + end + + context 'when the action is "mark_as_sensitive"' do + let(:action) { 'mark_as_sensitive' } + let(:statuses) { [media_attached_status, media_attached_deleted_status] } + + let!(:status) { Fabricate(:status, account: target_account) } + let(:media_attached_status) { Fabricate(:status, account: target_account) } + let!(:media_attachment) { Fabricate(:media_attachment, account: target_account, status: media_attached_status) } + let(:media_attached_deleted_status) { Fabricate(:status, account: target_account, deleted_at: 1.day.ago) } + let!(:media_attachment2) { Fabricate(:media_attachment, account: target_account, status: media_attached_deleted_status) } + let(:last_media_attached_status) { Fabricate(:status, account: target_account) } + let!(:last_media_attachment) { Fabricate(:media_attachment, account: target_account, status: last_media_attached_status) } + let!(:last_status) { Fabricate(:status, account: target_account) } + + it_behaves_like 'common behavior' + + it 'marks the non-deleted as sensitive' do + subject + expect(media_attached_status.reload.sensitive).to eq true + end + end + end + + context 'action as submit button' do + subject { post :create, params: { report_id: report.id, text: text, action => '' } } + it_behaves_like 'all action types' + end + + context 'action as submit button' do + subject { post :create, params: { report_id: report.id, text: text, moderation_action: action } } + it_behaves_like 'all action types' end end end -- cgit From 68dcbcb7bf2cceb181a9b82d604d2c2829c0c78b Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 18 Jan 2023 16:47:56 +0100 Subject: Add more specific error messages to HTTP signature verification (#21617) * Return specific error on failure to parse Date header * Add error message when preferredUsername is not set * Change error report to be JSON and include more details * Change error report to differentiate unknown account and failed refresh * Add tests --- app/controllers/concerns/signature_verification.rb | 16 +-- .../activitypub/fetch_remote_actor_service.rb | 1 + .../concerns/signature_verification_spec.rb | 107 +++++++++++++++++++-- 3 files changed, 109 insertions(+), 15 deletions(-) (limited to 'spec/controllers') diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 4502da698..a9950d21f 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -46,11 +46,11 @@ module SignatureVerification end def require_account_signature! - render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account + render json: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account end def require_actor_signature! - render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_actor + render json: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_actor end def signed_request? @@ -97,11 +97,11 @@ module SignatureVerification actor = stoplight_wrap_request { actor_refresh_key!(actor) } - raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil? + raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil? return actor unless verify_signature(actor, signature, compare_signed_string).nil? - fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)" + fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature'] rescue SignatureVerificationError => e fail_with! e.message rescue HTTP::Error, OpenSSL::SSL::SSLError => e @@ -118,8 +118,8 @@ module SignatureVerification private - def fail_with!(message) - @signature_verification_failure_reason = message + def fail_with!(message, **options) + @signature_verification_failure_reason = { error: message }.merge(options) @signed_request_actor = nil end @@ -209,8 +209,8 @@ module SignatureVerification end expires_time = Time.at(signature_params['expires'].to_i).utc if signature_params['expires'].present? - rescue ArgumentError - return false + rescue ArgumentError => e + raise SignatureVerificationError, "Invalid Date header: #{e.message}" end expires_time ||= created_time + 5.minutes unless created_time.nil? diff --git a/app/services/activitypub/fetch_remote_actor_service.rb b/app/services/activitypub/fetch_remote_actor_service.rb index a25fa54c4..4f60ea5e8 100644 --- a/app/services/activitypub/fetch_remote_actor_service.rb +++ b/app/services/activitypub/fetch_remote_actor_service.rb @@ -28,6 +28,7 @@ class ActivityPub::FetchRemoteActorService < BaseService raise Error, "Unsupported JSON-LD context for document #{uri}" unless supported_context? raise Error, "Unexpected object type for actor #{uri} (expected any of: #{SUPPORTED_TYPES})" unless expected_type? raise Error, "Actor #{uri} has moved to #{@json['movedTo']}" if break_on_redirect && @json['movedTo'].present? + raise Error, "Actor #{uri} has no 'preferredUsername', which is a requirement for Mastodon compatibility" unless @json['preferredUsername'].present? @uri = @json['id'] @username = @json['preferredUsername'] diff --git a/spec/controllers/concerns/signature_verification_spec.rb b/spec/controllers/concerns/signature_verification_spec.rb index 6e73643b4..13655f313 100644 --- a/spec/controllers/concerns/signature_verification_spec.rb +++ b/spec/controllers/concerns/signature_verification_spec.rb @@ -16,6 +16,8 @@ describe ApplicationController, type: :controller do controller do include SignatureVerification + before_action :require_actor_signature!, only: [:signature_required] + def success head 200 end @@ -23,10 +25,17 @@ describe ApplicationController, type: :controller do def alternative_success head 200 end + + def signature_required + head 200 + end end before do - routes.draw { match via: [:get, :post], 'success' => 'anonymous#success' } + routes.draw do + match via: [:get, :post], 'success' => 'anonymous#success' + match via: [:get, :post], 'signature_required' => 'anonymous#signature_required' + end end context 'without signature header' do @@ -118,6 +127,37 @@ describe ApplicationController, type: :controller do end end + context 'with request with unparseable Date header' do + before do + get :success + + fake_request = Request.new(:get, request.url) + fake_request.add_headers({ 'Date' => 'wrong date' }) + fake_request.on_behalf_of(author) + + request.headers.merge!(fake_request.headers) + end + + describe '#signed_request?' do + it 'returns true' do + expect(controller.signed_request?).to be true + end + end + + describe '#signed_request_account' do + it 'returns nil' do + expect(controller.signed_request_account).to be_nil + end + end + + describe '#signature_verification_failure_reason' do + it 'contains an error description' do + controller.signed_request_account + expect(controller.signature_verification_failure_reason[:error]).to eq 'Invalid Date header: not RFC 2616 compliant date: "wrong date"' + end + end + end + context 'with request older than a day' do before do get :success @@ -140,6 +180,13 @@ describe ApplicationController, type: :controller do expect(controller.signed_request_account).to be_nil end end + + describe '#signature_verification_failure_reason' do + it 'contains an error description' do + controller.signed_request_account + expect(controller.signature_verification_failure_reason[:error]).to eq 'Signed request date outside acceptable time window' + end + end end context 'with inaccessible key' do @@ -171,6 +218,7 @@ describe ApplicationController, type: :controller do context 'with body' do before do + allow(controller).to receive(:actor_refresh_key!).and_return(author) post :success, body: 'Hello world' fake_request = Request.new(:post, request.url, body: 'Hello world') @@ -189,21 +237,66 @@ describe ApplicationController, type: :controller do it 'returns an account' do expect(controller.signed_request_account).to eq author end + end - it 'returns nil when path does not match' do + context 'when path does not match' do + before do request.path = '/alternative-path' - expect(controller.signed_request_account).to be_nil end - it 'returns nil when method does not match' do + describe '#signed_request_account' do + it 'returns nil' do + expect(controller.signed_request_account).to be_nil + end + end + + describe '#signature_verification_failure_reason' do + it 'contains an error description' do + controller.signed_request_account + expect(controller.signature_verification_failure_reason[:error]).to include('using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)') + expect(controller.signature_verification_failure_reason[:signed_string]).to include("(request-target): post /alternative-path\n") + end + end + end + + context 'when method does not match' do + before do get :success - expect(controller.signed_request_account).to be_nil end - it 'returns nil when body has been tampered' do + describe '#signed_request_account' do + it 'returns nil' do + expect(controller.signed_request_account).to be_nil + end + end + end + + context 'when body has been tampered' do + before do post :success, body: 'doo doo doo' - expect(controller.signed_request_account).to be_nil end + + describe '#signed_request_account' do + it 'returns nil when body has been tampered' do + expect(controller.signed_request_account).to be_nil + end + end + end + end + end + + context 'when a signature is required' do + before do + get :signature_required + end + + context 'without signature header' do + it 'returns HTTP 401' do + expect(response).to have_http_status(401) + end + + it 'returns an error' do + expect(Oj.load(response.body)['error']).to eq 'Request not signed' end end end -- cgit