From c2231539c78103e000ef5edeeade9d3bbe54ac8d Mon Sep 17 00:00:00 2001 From: Emily Strickland Date: Sun, 13 Nov 2022 12:02:09 -0800 Subject: Test blank account field verifiability (#20458) * Test blank account field verifiability This change tests the need for #20428, which ensures that we guard against a situation in which `at_xpath` returns `nil`. * Test verifiability of blank fields for remote account profiles This adds a counterpart test for remote account profiles' fields' verifiability when those fields are blank. I previously added the same test for local accounts. --- spec/models/account/field_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'spec') diff --git a/spec/models/account/field_spec.rb b/spec/models/account/field_spec.rb index fcb2a884a..b4beec048 100644 --- a/spec/models/account/field_spec.rb +++ b/spec/models/account/field_spec.rb @@ -89,6 +89,14 @@ RSpec.describe Account::Field, type: :model do expect(subject.verifiable?).to be false end end + + context 'for text which is blank' do + let(:value) { '' } + + it 'returns false' do + expect(subject.verifiable?).to be false + end + end end context 'for remote accounts' do @@ -133,6 +141,14 @@ RSpec.describe Account::Field, type: :model do expect(subject.verifiable?).to be false end end + + context 'for text which is blank' do + let(:value) { '' } + + it 'returns false' do + expect(subject.verifiable?).to be false + end + end end end end -- cgit From 147d8bd8fc7aa8b55cd5a9103941c52441ed4365 Mon Sep 17 00:00:00 2001 From: Hampton Lintorn-Catlin Date: Sun, 13 Nov 2022 23:52:13 -0500 Subject: Support UTF-8 Characters in Domains During CSV Import (#20592) * Support UTF-8 Characters in Domains During Import * Update Changelong --- CHANGELOG.md | 1 + app/services/import_service.rb | 2 +- spec/fixtures/files/utf8-followers.txt | 1 + spec/services/import_service_spec.rb | 23 +++++++++++++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/files/utf8-followers.txt (limited to 'spec') diff --git a/CHANGELOG.md b/CHANGELOG.md index 72f62a1dc..93f05a650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -185,6 +185,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix `CDN_HOST` not being used in some asset URLs ([tribela](https://github.com/mastodon/mastodon/pull/18662)) - Fix `CAS_DISPLAY_NAME`, `SAML_DISPLAY_NAME` and `OIDC_DISPLAY_NAME` being ignored ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18568)) - Fix various typos in comments throughout the codebase ([luzpaz](https://github.com/mastodon/mastodon/pull/18604)) +- Fix CSV upload no longer breaks if an server domain includes UTF-8 characters ([HamptonMakes]()) ### Security diff --git a/app/services/import_service.rb b/app/services/import_service.rb index ece5b9ef0..2f48abc36 100644 --- a/app/services/import_service.rb +++ b/app/services/import_service.rb @@ -136,7 +136,7 @@ class ImportService < BaseService end def import_data - Paperclip.io_adapters.for(@import.data).read + Paperclip.io_adapters.for(@import.data).read.force_encoding(Encoding::UTF_8) end def relations_map_for_account(account, account_ids) diff --git a/spec/fixtures/files/utf8-followers.txt b/spec/fixtures/files/utf8-followers.txt new file mode 100644 index 000000000..9d4fe3485 --- /dev/null +++ b/spec/fixtures/files/utf8-followers.txt @@ -0,0 +1 @@ +@nare@թութ.հայ diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb index 764225aa7..e2d182920 100644 --- a/spec/services/import_service_spec.rb +++ b/spec/services/import_service_spec.rb @@ -172,6 +172,29 @@ RSpec.describe ImportService, type: :service do end end + # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users + # + # https://github.com/mastodon/mastodon/issues/20571 + context 'utf-8 encoded domains' do + subject { ImportService.new } + + let!(:nare) { Fabricate(:account, username: 'nare', domain: 'թութ.հայ', locked: false, protocol: :activitypub, inbox_url: 'https://թութ.հայ/inbox') } + + # Make sure to not actually go to the remote server + before do + stub_request(:post, "https://թութ.հայ/inbox").to_return(status: 200) + end + + let(:csv) { attachment_fixture('utf8-followers.txt') } + let(:import) { Import.create(account: account, type: 'following', data: csv) } + + it 'follows the listed account' do + expect(account.follow_requests.count).to eq 0 + subject.call(import) + expect(account.follow_requests.count).to eq 1 + end + end + context 'import bookmarks' do subject { ImportService.new } -- cgit From b31afc62943b79bf97338040e39123b9dd68f31f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 14 Nov 2022 08:06:06 +0100 Subject: Fix error when passing unknown filter param in REST API (#20626) Fix #19156 --- app/controllers/api/base_controller.rb | 2 +- app/models/account_filter.rb | 10 ++++++---- app/models/admin/action_log_filter.rb | 2 +- app/models/admin/appeal_filter.rb | 4 ++-- app/models/admin/status_filter.rb | 2 +- app/models/announcement_filter.rb | 2 +- app/models/custom_emoji_filter.rb | 2 +- app/models/instance_filter.rb | 4 ++-- app/models/invite_filter.rb | 2 +- app/models/relationship_filter.rb | 12 ++++++------ app/models/report_filter.rb | 4 ++-- app/models/trends/preview_card_filter.rb | 2 +- app/models/trends/preview_card_provider_filter.rb | 4 ++-- app/models/trends/status_filter.rb | 2 +- lib/exceptions.rb | 1 + spec/models/custom_emoji_filter_spec.rb | 4 ++-- 16 files changed, 31 insertions(+), 28 deletions(-) (limited to 'spec') diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 3f3e1ca7b..665425f29 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -57,7 +57,7 @@ class Api::BaseController < ApplicationController render json: { error: I18n.t('errors.429') }, status: 429 end - rescue_from ActionController::ParameterMissing do |e| + rescue_from ActionController::ParameterMissing, Mastodon::InvalidParameterError do |e| render json: { error: e.to_s }, status: 400 end diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index e214e0bad..e09ce4ec2 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -57,7 +57,7 @@ class AccountFilter when 'order' order_scope(value) else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end @@ -68,7 +68,7 @@ class AccountFilter when 'remote' Account.remote else - raise "Unknown origin: #{value}" + raise Mastodon::InvalidParameterError, "Unknown origin: #{value}" end end @@ -84,8 +84,10 @@ class AccountFilter accounts_with_users.merge(User.disabled) when 'silenced' Account.silenced + when 'sensitized' + Account.sensitized else - raise "Unknown status: #{value}" + raise Mastodon::InvalidParameterError, "Unknown status: #{value}" end end @@ -96,7 +98,7 @@ class AccountFilter when 'recent' Account.recent else - raise "Unknown order: #{value}" + raise Mastodon::InvalidParameterError, "Unknown order: #{value}" end end diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb index edb391e2e..f89d452ef 100644 --- a/app/models/admin/action_log_filter.rb +++ b/app/models/admin/action_log_filter.rb @@ -95,7 +95,7 @@ class Admin::ActionLogFilter account = Account.find_or_initialize_by(id: value) Admin::ActionLog.where(target: [account, account.user].compact) else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end end diff --git a/app/models/admin/appeal_filter.rb b/app/models/admin/appeal_filter.rb index b163d2e56..f5dcc0f54 100644 --- a/app/models/admin/appeal_filter.rb +++ b/app/models/admin/appeal_filter.rb @@ -30,7 +30,7 @@ class Admin::AppealFilter when 'status' status_scope(value) else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end @@ -43,7 +43,7 @@ class Admin::AppealFilter when 'pending' Appeal.pending else - raise "Unknown status: #{value}" + raise Mastodon::InvalidParameterError, "Unknown status: #{value}" end end end diff --git a/app/models/admin/status_filter.rb b/app/models/admin/status_filter.rb index d7a16f760..4d439e9a1 100644 --- a/app/models/admin/status_filter.rb +++ b/app/models/admin/status_filter.rb @@ -32,7 +32,7 @@ class Admin::StatusFilter when 'media' Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id).reorder('statuses.id desc') else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end end diff --git a/app/models/announcement_filter.rb b/app/models/announcement_filter.rb index 950852460..85c3b1d2c 100644 --- a/app/models/announcement_filter.rb +++ b/app/models/announcement_filter.rb @@ -33,7 +33,7 @@ class AnnouncementFilter when 'unpublished' Announcement.unpublished else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end end diff --git a/app/models/custom_emoji_filter.rb b/app/models/custom_emoji_filter.rb index 414e1fcdd..ed7a8dda1 100644 --- a/app/models/custom_emoji_filter.rb +++ b/app/models/custom_emoji_filter.rb @@ -39,7 +39,7 @@ class CustomEmojiFilter when 'shortcode' CustomEmoji.search(value.strip) else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end end diff --git a/app/models/instance_filter.rb b/app/models/instance_filter.rb index e7e5166a1..1d94c919f 100644 --- a/app/models/instance_filter.rb +++ b/app/models/instance_filter.rb @@ -36,7 +36,7 @@ class InstanceFilter when 'availability' availability_scope(value) else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end @@ -47,7 +47,7 @@ class InstanceFilter when 'unavailable' Instance.joins(:unavailable_domain) else - raise "Unknown availability: #{value}" + raise Mastodon::InvalidParameterError, "Unknown availability: #{value}" end end end diff --git a/app/models/invite_filter.rb b/app/models/invite_filter.rb index 9685d4abb..c1edb3871 100644 --- a/app/models/invite_filter.rb +++ b/app/models/invite_filter.rb @@ -31,7 +31,7 @@ class InviteFilter when 'expired' Invite.expired else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end end diff --git a/app/models/relationship_filter.rb b/app/models/relationship_filter.rb index 9135ff144..249fe3df8 100644 --- a/app/models/relationship_filter.rb +++ b/app/models/relationship_filter.rb @@ -53,7 +53,7 @@ class RelationshipFilter when 'activity' activity_scope(value) else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end @@ -68,7 +68,7 @@ class RelationshipFilter when 'invited' Account.joins(user: :invite).merge(Invite.where(user: account.user)).eager_load(:account_stat).reorder(nil) else - raise "Unknown relationship: #{value}" + raise Mastodon::InvalidParameterError, "Unknown relationship: #{value}" end end @@ -83,7 +83,7 @@ class RelationshipFilter when 'remote' Account.remote else - raise "Unknown location: #{value}" + raise Mastodon::InvalidParameterError, "Unknown location: #{value}" end end @@ -94,7 +94,7 @@ class RelationshipFilter when 'primary' Account.where(moved_to_account_id: nil) else - raise "Unknown status: #{value}" + raise Mastodon::InvalidParameterError, "Unknown status: #{value}" end end @@ -105,7 +105,7 @@ class RelationshipFilter when 'recent' params[:relationship] == 'invited' ? Account.recent : Follow.recent else - raise "Unknown order: #{value}" + raise Mastodon::InvalidParameterError, "Unknown order: #{value}" end end @@ -114,7 +114,7 @@ class RelationshipFilter when 'dormant' AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago))) else - raise "Unknown activity: #{value}" + raise Mastodon::InvalidParameterError, "Unknown activity: #{value}" end end end diff --git a/app/models/report_filter.rb b/app/models/report_filter.rb index dc444a552..c9b3bce2d 100644 --- a/app/models/report_filter.rb +++ b/app/models/report_filter.rb @@ -38,7 +38,7 @@ class ReportFilter when :target_origin target_origin_scope(value) else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end @@ -49,7 +49,7 @@ class ReportFilter when :remote Report.where(target_account: Account.remote) else - raise "Unknown value: #{value}" + raise Mastodon::InvalidParameterError, "Unknown value: #{value}" end end end diff --git a/app/models/trends/preview_card_filter.rb b/app/models/trends/preview_card_filter.rb index 0a81146d4..f0214c3f0 100644 --- a/app/models/trends/preview_card_filter.rb +++ b/app/models/trends/preview_card_filter.rb @@ -40,7 +40,7 @@ class Trends::PreviewCardFilter when 'locale' PreviewCardTrend.where(language: value) else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end diff --git a/app/models/trends/preview_card_provider_filter.rb b/app/models/trends/preview_card_provider_filter.rb index abfdd07e8..219793f01 100644 --- a/app/models/trends/preview_card_provider_filter.rb +++ b/app/models/trends/preview_card_provider_filter.rb @@ -30,7 +30,7 @@ class Trends::PreviewCardProviderFilter when 'status' status_scope(value) else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end @@ -43,7 +43,7 @@ class Trends::PreviewCardProviderFilter when 'pending_review' PreviewCardProvider.pending_review else - raise "Unknown status: #{value}" + raise Mastodon::InvalidParameterError, "Unknown status: #{value}" end end end diff --git a/app/models/trends/status_filter.rb b/app/models/trends/status_filter.rb index cb0f75d67..de435a026 100644 --- a/app/models/trends/status_filter.rb +++ b/app/models/trends/status_filter.rb @@ -40,7 +40,7 @@ class Trends::StatusFilter when 'locale' StatusTrend.where(language: value) else - raise "Unknown filter: #{key}" + raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" end end diff --git a/lib/exceptions.rb b/lib/exceptions.rb index 3c5ba226b..d3b92f4a0 100644 --- a/lib/exceptions.rb +++ b/lib/exceptions.rb @@ -11,6 +11,7 @@ module Mastodon class RaceConditionError < Error; end class RateLimitExceededError < Error; end class SyntaxError < Error; end + class InvalidParameterError < Error; end class UnexpectedResponseError < Error attr_reader :response diff --git a/spec/models/custom_emoji_filter_spec.rb b/spec/models/custom_emoji_filter_spec.rb index d859f5c5f..2b1b5dc54 100644 --- a/spec/models/custom_emoji_filter_spec.rb +++ b/spec/models/custom_emoji_filter_spec.rb @@ -50,10 +50,10 @@ RSpec.describe CustomEmojiFilter do context 'else' do let(:params) { { else: 'else' } } - it 'raises RuntimeError' do + it 'raises Mastodon::InvalidParameterError' do expect do subject - end.to raise_error(RuntimeError, /Unknown filter: else/) + end.to raise_error(Mastodon::InvalidParameterError, /Unknown filter: else/) end end end -- cgit From 552d69ad96fec7ebfca46a97c50355678e114223 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 14 Nov 2022 08:07:14 +0100 Subject: Fix error when invalid domain name is submitted (#19474) Fix #19175 --- app/models/concerns/domain_normalizable.rb | 2 + .../api/v1/admin/domain_allows_controller_spec.rb | 44 ++++++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) (limited to 'spec') diff --git a/app/models/concerns/domain_normalizable.rb b/app/models/concerns/domain_normalizable.rb index fb84058fc..8e244c1d8 100644 --- a/app/models/concerns/domain_normalizable.rb +++ b/app/models/concerns/domain_normalizable.rb @@ -11,5 +11,7 @@ module DomainNormalizable def normalize_domain self.domain = TagManager.instance.normalize_domain(domain&.strip) + rescue Addressable::URI::InvalidURIError + errors.add(:domain, :invalid) end end diff --git a/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb b/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb index 26a391a60..8100363f6 100644 --- a/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb +++ b/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb @@ -94,25 +94,37 @@ RSpec.describe Api::V1::Admin::DomainAllowsController, type: :controller do describe 'POST #create' do let!(:domain_allow) { Fabricate(:domain_allow, domain: 'example.com') } - before do - post :create, params: { domain: 'foo.bar.com' } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - - it 'returns http success' do - expect(response).to have_http_status(200) + context do + before do + post :create, params: { domain: 'foo.bar.com' } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns expected domain name' do + json = body_as_json + expect(json[:domain]).to eq 'foo.bar.com' + end + + it 'creates a domain block' do + expect(DomainAllow.find_by(domain: 'foo.bar.com')).to_not be_nil + end end - it 'returns expected domain name' do - json = body_as_json - expect(json[:domain]).to eq 'foo.bar.com' - end + context 'with invalid domain name' do + before do + post :create, params: { domain: 'foo bar' } + end - it 'creates a domain block' do - expect(DomainAllow.find_by(domain: 'foo.bar.com')).to_not be_nil + it 'returns http unprocessable entity' do + expect(response).to have_http_status(422) + end end end end -- cgit From b59ce0a60ff4f90bb16a8c3338ad37218af052b8 Mon Sep 17 00:00:00 2001 From: trwnh Date: Mon, 14 Nov 2022 01:34:07 -0600 Subject: Move V2 Filter methods under /api/v2 prefix (#20622) * Move V2 Filter methods under /api/v2 prefix * move over the tests too --- .../api/v1/filters/keywords_controller.rb | 50 -------- .../api/v1/filters/statuses_controller.rb | 44 ------- .../api/v2/filters/keywords_controller.rb | 50 ++++++++ .../api/v2/filters/statuses_controller.rb | 44 +++++++ app/javascript/mastodon/actions/filters.js | 2 +- config/routes.rb | 20 +-- .../api/v1/filters/keywords_controller_spec.rb | 142 --------------------- .../api/v1/filters/statuses_controller_spec.rb | 116 ----------------- .../api/v2/filters/keywords_controller_spec.rb | 142 +++++++++++++++++++++ .../api/v2/filters/statuses_controller_spec.rb | 116 +++++++++++++++++ 10 files changed, 363 insertions(+), 363 deletions(-) delete mode 100644 app/controllers/api/v1/filters/keywords_controller.rb delete mode 100644 app/controllers/api/v1/filters/statuses_controller.rb create mode 100644 app/controllers/api/v2/filters/keywords_controller.rb create mode 100644 app/controllers/api/v2/filters/statuses_controller.rb delete mode 100644 spec/controllers/api/v1/filters/keywords_controller_spec.rb delete mode 100644 spec/controllers/api/v1/filters/statuses_controller_spec.rb create mode 100644 spec/controllers/api/v2/filters/keywords_controller_spec.rb create mode 100644 spec/controllers/api/v2/filters/statuses_controller_spec.rb (limited to 'spec') diff --git a/app/controllers/api/v1/filters/keywords_controller.rb b/app/controllers/api/v1/filters/keywords_controller.rb deleted file mode 100644 index d3718a137..000000000 --- a/app/controllers/api/v1/filters/keywords_controller.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Filters::KeywordsController < Api::BaseController - before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show] - before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show] - before_action :require_user! - - before_action :set_keywords, only: :index - before_action :set_keyword, only: [:show, :update, :destroy] - - def index - render json: @keywords, each_serializer: REST::FilterKeywordSerializer - end - - def create - @keyword = current_account.custom_filters.find(params[:filter_id]).keywords.create!(resource_params) - - render json: @keyword, serializer: REST::FilterKeywordSerializer - end - - def show - render json: @keyword, serializer: REST::FilterKeywordSerializer - end - - def update - @keyword.update!(resource_params) - - render json: @keyword, serializer: REST::FilterKeywordSerializer - end - - def destroy - @keyword.destroy! - render_empty - end - - private - - def set_keywords - filter = current_account.custom_filters.includes(:keywords).find(params[:filter_id]) - @keywords = filter.keywords - end - - def set_keyword - @keyword = CustomFilterKeyword.includes(:custom_filter).where(custom_filter: { account: current_account }).find(params[:id]) - end - - def resource_params - params.permit(:keyword, :whole_word) - end -end diff --git a/app/controllers/api/v1/filters/statuses_controller.rb b/app/controllers/api/v1/filters/statuses_controller.rb deleted file mode 100644 index b6bed306f..000000000 --- a/app/controllers/api/v1/filters/statuses_controller.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Filters::StatusesController < Api::BaseController - before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show] - before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show] - before_action :require_user! - - before_action :set_status_filters, only: :index - before_action :set_status_filter, only: [:show, :destroy] - - def index - render json: @status_filters, each_serializer: REST::FilterStatusSerializer - end - - def create - @status_filter = current_account.custom_filters.find(params[:filter_id]).statuses.create!(resource_params) - - render json: @status_filter, serializer: REST::FilterStatusSerializer - end - - def show - render json: @status_filter, serializer: REST::FilterStatusSerializer - end - - def destroy - @status_filter.destroy! - render_empty - end - - private - - def set_status_filters - filter = current_account.custom_filters.includes(:statuses).find(params[:filter_id]) - @status_filters = filter.statuses - end - - def set_status_filter - @status_filter = CustomFilterStatus.includes(:custom_filter).where(custom_filter: { account: current_account }).find(params[:id]) - end - - def resource_params - params.permit(:status_id) - end -end diff --git a/app/controllers/api/v2/filters/keywords_controller.rb b/app/controllers/api/v2/filters/keywords_controller.rb new file mode 100644 index 000000000..c63e1d986 --- /dev/null +++ b/app/controllers/api/v2/filters/keywords_controller.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +class Api::V2::Filters::KeywordsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show] + before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show] + before_action :require_user! + + before_action :set_keywords, only: :index + before_action :set_keyword, only: [:show, :update, :destroy] + + def index + render json: @keywords, each_serializer: REST::FilterKeywordSerializer + end + + def create + @keyword = current_account.custom_filters.find(params[:filter_id]).keywords.create!(resource_params) + + render json: @keyword, serializer: REST::FilterKeywordSerializer + end + + def show + render json: @keyword, serializer: REST::FilterKeywordSerializer + end + + def update + @keyword.update!(resource_params) + + render json: @keyword, serializer: REST::FilterKeywordSerializer + end + + def destroy + @keyword.destroy! + render_empty + end + + private + + def set_keywords + filter = current_account.custom_filters.includes(:keywords).find(params[:filter_id]) + @keywords = filter.keywords + end + + def set_keyword + @keyword = CustomFilterKeyword.includes(:custom_filter).where(custom_filter: { account: current_account }).find(params[:id]) + end + + def resource_params + params.permit(:keyword, :whole_word) + end +end diff --git a/app/controllers/api/v2/filters/statuses_controller.rb b/app/controllers/api/v2/filters/statuses_controller.rb new file mode 100644 index 000000000..755c14cff --- /dev/null +++ b/app/controllers/api/v2/filters/statuses_controller.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +class Api::V2::Filters::StatusesController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show] + before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show] + before_action :require_user! + + before_action :set_status_filters, only: :index + before_action :set_status_filter, only: [:show, :destroy] + + def index + render json: @status_filters, each_serializer: REST::FilterStatusSerializer + end + + def create + @status_filter = current_account.custom_filters.find(params[:filter_id]).statuses.create!(resource_params) + + render json: @status_filter, serializer: REST::FilterStatusSerializer + end + + def show + render json: @status_filter, serializer: REST::FilterStatusSerializer + end + + def destroy + @status_filter.destroy! + render_empty + end + + private + + def set_status_filters + filter = current_account.custom_filters.includes(:statuses).find(params[:filter_id]) + @status_filters = filter.statuses + end + + def set_status_filter + @status_filter = CustomFilterStatus.includes(:custom_filter).where(custom_filter: { account: current_account }).find(params[:id]) + end + + def resource_params + params.permit(:status_id) + end +end diff --git a/app/javascript/mastodon/actions/filters.js b/app/javascript/mastodon/actions/filters.js index 76326802e..e9c609fc8 100644 --- a/app/javascript/mastodon/actions/filters.js +++ b/app/javascript/mastodon/actions/filters.js @@ -43,7 +43,7 @@ export const fetchFilters = () => (dispatch, getState) => { export const createFilterStatus = (params, onSuccess, onFail) => (dispatch, getState) => { dispatch(createFilterStatusRequest()); - api(getState).post(`/api/v1/filters/${params.filter_id}/statuses`, params).then(response => { + api(getState).post(`/api/v2/filters/${params.filter_id}/statuses`, params).then(response => { dispatch(createFilterStatusSuccess(response.data)); if (onSuccess) onSuccess(); }).catch(error => { diff --git a/config/routes.rb b/config/routes.rb index d8af292bf..f095ff655 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -493,18 +493,10 @@ Rails.application.routes.draw do resources :bookmarks, only: [:index] resources :reports, only: [:create] resources :trends, only: [:index], controller: 'trends/tags' - resources :filters, only: [:index, :create, :show, :update, :destroy] do - resources :keywords, only: [:index, :create], controller: 'filters/keywords' - resources :statuses, only: [:index, :create], controller: 'filters/statuses' - end + resources :filters, only: [:index, :create, :show, :update, :destroy] resources :endorsements, only: [:index] resources :markers, only: [:index, :create] - namespace :filters do - resources :keywords, only: [:show, :update, :destroy] - resources :statuses, only: [:show, :destroy] - end - namespace :apps do get :verify_credentials, to: 'credentials#show' end @@ -660,8 +652,16 @@ Rails.application.routes.draw do resources :media, only: [:create] resources :suggestions, only: [:index] - resources :filters, only: [:index, :create, :show, :update, :destroy] resource :instance, only: [:show] + resources :filters, only: [:index, :create, :show, :update, :destroy] do + resources :keywords, only: [:index, :create], controller: 'filters/keywords' + resources :statuses, only: [:index, :create], controller: 'filters/statuses' + end + + namespace :filters do + resources :keywords, only: [:show, :update, :destroy] + resources :statuses, only: [:show, :destroy] + end namespace :admin do resources :accounts, only: [:index] diff --git a/spec/controllers/api/v1/filters/keywords_controller_spec.rb b/spec/controllers/api/v1/filters/keywords_controller_spec.rb deleted file mode 100644 index aecb4e41c..000000000 --- a/spec/controllers/api/v1/filters/keywords_controller_spec.rb +++ /dev/null @@ -1,142 +0,0 @@ -require 'rails_helper' - -RSpec.describe Api::V1::Filters::KeywordsController, type: :controller do - render_views - - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:filter) { Fabricate(:custom_filter, account: user.account) } - let(:other_user) { Fabricate(:user) } - let(:other_filter) { Fabricate(:custom_filter, account: other_user.account) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'GET #index' do - let(:scopes) { 'read:filters' } - let!(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) } - - it 'returns http success' do - get :index, params: { filter_id: filter.id } - expect(response).to have_http_status(200) - end - - context "when trying to access another's user filters" do - it 'returns http not found' do - get :index, params: { filter_id: other_filter.id } - expect(response).to have_http_status(404) - end - end - end - - describe 'POST #create' do - let(:scopes) { 'write:filters' } - let(:filter_id) { filter.id } - - before do - post :create, params: { filter_id: filter_id, keyword: 'magic', whole_word: false } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns a keyword' do - json = body_as_json - expect(json[:keyword]).to eq 'magic' - expect(json[:whole_word]).to eq false - end - - it 'creates a keyword' do - filter = user.account.custom_filters.first - expect(filter).to_not be_nil - expect(filter.keywords.pluck(:keyword)).to eq ['magic'] - end - - context "when trying to add to another another's user filters" do - let(:filter_id) { other_filter.id } - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end - - describe 'GET #show' do - let(:scopes) { 'read:filters' } - let(:keyword) { Fabricate(:custom_filter_keyword, keyword: 'foo', whole_word: false, custom_filter: filter) } - - before do - get :show, params: { id: keyword.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns expected data' do - json = body_as_json - expect(json[:keyword]).to eq 'foo' - expect(json[:whole_word]).to eq false - end - - context "when trying to access another user's filter keyword" do - let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: other_filter) } - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end - - describe 'PUT #update' do - let(:scopes) { 'write:filters' } - let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) } - - before do - get :update, params: { id: keyword.id, keyword: 'updated' } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'updates the keyword' do - expect(keyword.reload.keyword).to eq 'updated' - end - - context "when trying to update another user's filter keyword" do - let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: other_filter) } - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end - - describe 'DELETE #destroy' do - let(:scopes) { 'write:filters' } - let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) } - - before do - delete :destroy, params: { id: keyword.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'removes the filter' do - expect { keyword.reload }.to raise_error ActiveRecord::RecordNotFound - end - - context "when trying to update another user's filter keyword" do - let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: other_filter) } - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end -end diff --git a/spec/controllers/api/v1/filters/statuses_controller_spec.rb b/spec/controllers/api/v1/filters/statuses_controller_spec.rb deleted file mode 100644 index 3b2399dd8..000000000 --- a/spec/controllers/api/v1/filters/statuses_controller_spec.rb +++ /dev/null @@ -1,116 +0,0 @@ -require 'rails_helper' - -RSpec.describe Api::V1::Filters::StatusesController, type: :controller do - render_views - - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:filter) { Fabricate(:custom_filter, account: user.account) } - let(:other_user) { Fabricate(:user) } - let(:other_filter) { Fabricate(:custom_filter, account: other_user.account) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'GET #index' do - let(:scopes) { 'read:filters' } - let!(:status_filter) { Fabricate(:custom_filter_status, custom_filter: filter) } - - it 'returns http success' do - get :index, params: { filter_id: filter.id } - expect(response).to have_http_status(200) - end - - context "when trying to access another's user filters" do - it 'returns http not found' do - get :index, params: { filter_id: other_filter.id } - expect(response).to have_http_status(404) - end - end - end - - describe 'POST #create' do - let(:scopes) { 'write:filters' } - let(:filter_id) { filter.id } - let!(:status) { Fabricate(:status) } - - before do - post :create, params: { filter_id: filter_id, status_id: status.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns a status filter' do - json = body_as_json - expect(json[:status_id]).to eq status.id.to_s - end - - it 'creates a status filter' do - filter = user.account.custom_filters.first - expect(filter).to_not be_nil - expect(filter.statuses.pluck(:status_id)).to eq [status.id] - end - - context "when trying to add to another another's user filters" do - let(:filter_id) { other_filter.id } - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end - - describe 'GET #show' do - let(:scopes) { 'read:filters' } - let!(:status_filter) { Fabricate(:custom_filter_status, custom_filter: filter) } - - before do - get :show, params: { id: status_filter.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns expected data' do - json = body_as_json - expect(json[:status_id]).to eq status_filter.status_id.to_s - end - - context "when trying to access another user's filter keyword" do - let(:status_filter) { Fabricate(:custom_filter_status, custom_filter: other_filter) } - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end - - describe 'DELETE #destroy' do - let(:scopes) { 'write:filters' } - let(:status_filter) { Fabricate(:custom_filter_status, custom_filter: filter) } - - before do - delete :destroy, params: { id: status_filter.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'removes the filter' do - expect { status_filter.reload }.to raise_error ActiveRecord::RecordNotFound - end - - context "when trying to update another user's filter keyword" do - let(:status_filter) { Fabricate(:custom_filter_status, custom_filter: other_filter) } - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - end -end diff --git a/spec/controllers/api/v2/filters/keywords_controller_spec.rb b/spec/controllers/api/v2/filters/keywords_controller_spec.rb new file mode 100644 index 000000000..1201a4ca2 --- /dev/null +++ b/spec/controllers/api/v2/filters/keywords_controller_spec.rb @@ -0,0 +1,142 @@ +require 'rails_helper' + +RSpec.describe Api::V2::Filters::KeywordsController, type: :controller do + render_views + + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:filter) { Fabricate(:custom_filter, account: user.account) } + let(:other_user) { Fabricate(:user) } + let(:other_filter) { Fabricate(:custom_filter, account: other_user.account) } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + describe 'GET #index' do + let(:scopes) { 'read:filters' } + let!(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) } + + it 'returns http success' do + get :index, params: { filter_id: filter.id } + expect(response).to have_http_status(200) + end + + context "when trying to access another's user filters" do + it 'returns http not found' do + get :index, params: { filter_id: other_filter.id } + expect(response).to have_http_status(404) + end + end + end + + describe 'POST #create' do + let(:scopes) { 'write:filters' } + let(:filter_id) { filter.id } + + before do + post :create, params: { filter_id: filter_id, keyword: 'magic', whole_word: false } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns a keyword' do + json = body_as_json + expect(json[:keyword]).to eq 'magic' + expect(json[:whole_word]).to eq false + end + + it 'creates a keyword' do + filter = user.account.custom_filters.first + expect(filter).to_not be_nil + expect(filter.keywords.pluck(:keyword)).to eq ['magic'] + end + + context "when trying to add to another another's user filters" do + let(:filter_id) { other_filter.id } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + describe 'GET #show' do + let(:scopes) { 'read:filters' } + let(:keyword) { Fabricate(:custom_filter_keyword, keyword: 'foo', whole_word: false, custom_filter: filter) } + + before do + get :show, params: { id: keyword.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns expected data' do + json = body_as_json + expect(json[:keyword]).to eq 'foo' + expect(json[:whole_word]).to eq false + end + + context "when trying to access another user's filter keyword" do + let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: other_filter) } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + describe 'PUT #update' do + let(:scopes) { 'write:filters' } + let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) } + + before do + get :update, params: { id: keyword.id, keyword: 'updated' } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'updates the keyword' do + expect(keyword.reload.keyword).to eq 'updated' + end + + context "when trying to update another user's filter keyword" do + let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: other_filter) } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + describe 'DELETE #destroy' do + let(:scopes) { 'write:filters' } + let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) } + + before do + delete :destroy, params: { id: keyword.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'removes the filter' do + expect { keyword.reload }.to raise_error ActiveRecord::RecordNotFound + end + + context "when trying to update another user's filter keyword" do + let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: other_filter) } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/controllers/api/v2/filters/statuses_controller_spec.rb b/spec/controllers/api/v2/filters/statuses_controller_spec.rb new file mode 100644 index 000000000..9740c1eb3 --- /dev/null +++ b/spec/controllers/api/v2/filters/statuses_controller_spec.rb @@ -0,0 +1,116 @@ +require 'rails_helper' + +RSpec.describe Api::V2::Filters::StatusesController, type: :controller do + render_views + + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:filter) { Fabricate(:custom_filter, account: user.account) } + let(:other_user) { Fabricate(:user) } + let(:other_filter) { Fabricate(:custom_filter, account: other_user.account) } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + describe 'GET #index' do + let(:scopes) { 'read:filters' } + let!(:status_filter) { Fabricate(:custom_filter_status, custom_filter: filter) } + + it 'returns http success' do + get :index, params: { filter_id: filter.id } + expect(response).to have_http_status(200) + end + + context "when trying to access another's user filters" do + it 'returns http not found' do + get :index, params: { filter_id: other_filter.id } + expect(response).to have_http_status(404) + end + end + end + + describe 'POST #create' do + let(:scopes) { 'write:filters' } + let(:filter_id) { filter.id } + let!(:status) { Fabricate(:status) } + + before do + post :create, params: { filter_id: filter_id, status_id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns a status filter' do + json = body_as_json + expect(json[:status_id]).to eq status.id.to_s + end + + it 'creates a status filter' do + filter = user.account.custom_filters.first + expect(filter).to_not be_nil + expect(filter.statuses.pluck(:status_id)).to eq [status.id] + end + + context "when trying to add to another another's user filters" do + let(:filter_id) { other_filter.id } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + describe 'GET #show' do + let(:scopes) { 'read:filters' } + let!(:status_filter) { Fabricate(:custom_filter_status, custom_filter: filter) } + + before do + get :show, params: { id: status_filter.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns expected data' do + json = body_as_json + expect(json[:status_id]).to eq status_filter.status_id.to_s + end + + context "when trying to access another user's filter keyword" do + let(:status_filter) { Fabricate(:custom_filter_status, custom_filter: other_filter) } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + describe 'DELETE #destroy' do + let(:scopes) { 'write:filters' } + let(:status_filter) { Fabricate(:custom_filter_status, custom_filter: filter) } + + before do + delete :destroy, params: { id: status_filter.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'removes the filter' do + expect { status_filter.reload }.to raise_error ActiveRecord::RecordNotFound + end + + context "when trying to update another user's filter keyword" do + let(:status_filter) { Fabricate(:custom_filter_status, custom_filter: other_filter) } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end +end -- cgit