From ffaa814bbe202de7e9f594698f6b1297fd950255 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Thu, 23 Aug 2018 19:30:09 +0200 Subject: [PATCH] Use backend from glitch-soc for instance-only toots --- app/controllers/accounts_controller.rb | 2 +- app/controllers/stream_entries_controller.rb | 4 +- app/models/status.rb | 6 ++- app/models/stream_entry.rb | 2 +- app/policies/status_policy.rb | 6 +++ app/services/post_status_service.rb | 9 ++-- app/services/reblog_service.rb | 7 ++- ...0213213_add_local_only_flag_to_statuses.rb | 5 ++ db/schema.rb | 1 + spec/models/status_spec.rb | 47 +++++++++++++++++++ spec/policies/status_policy_spec.rb | 12 +++++ 11 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 db/migrate/20171210213213_add_local_only_flag_to_statuses.rb diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index f788a9078..52753c1c3 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -35,7 +35,7 @@ class AccountsController < ApplicationController end format.rss do - @statuses = cache_collection(default_statuses.without_reblogs.without_replies.limit(PAGE_SIZE), Status) + @statuses = cache_collection(default_statuses.without_local_only.without_reblogs.without_replies.limit(PAGE_SIZE), Status) render xml: RSS::AccountSerializer.render(@account, @statuses) end diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index 8568b151c..24435cf8a 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -19,7 +19,7 @@ class StreamEntriesController < ApplicationController end format.atom do - unless @stream_entry.hidden? + unless @stream_entry.hidden? || @stream_entry.local_only? skip_session! expires_in 3.minutes, public: true end @@ -53,7 +53,7 @@ class StreamEntriesController < ApplicationController @type = @stream_entry.activity_type.downcase raise ActiveRecord::RecordNotFound if @stream_entry.activity.nil? - authorize @stream_entry.activity, :show? if @stream_entry.hidden? + authorize @stream_entry.activity, :show? if @stream_entry.hidden? || @stream_entry.local_only? rescue Mastodon::NotPermittedError # Reraise in order to get a 404 raise ActiveRecord::RecordNotFound diff --git a/app/models/status.rb b/app/models/status.rb index 35655bff2..90534bab8 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -21,6 +21,7 @@ # account_id :bigint(8) not null # application_id :bigint(8) # in_reply_to_account_id :bigint(8) +# local_only :boolean # class Status < ApplicationRecord @@ -73,6 +74,7 @@ class Status < ApplicationRecord scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') } scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') } + scope :without_local_only, -> { where(local_only: [false, nil]) } scope :with_public_visibility, -> { where(visibility: :public) } scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) } scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: false }) } @@ -336,7 +338,7 @@ class Status < ApplicationRecord visibility = [:public, :unlisted] if account.nil? - where(visibility: visibility) + where(visibility: visibility).without_local_only elsif target_account.blocking?(account) # get rid of blocked peeps none elsif account.id == target_account.id # author can see own stuff @@ -379,7 +381,7 @@ class Status < ApplicationRecord end def filter_timeline_default(query) - query.excluding_silenced_accounts + query.without_local_only.excluding_silenced_accounts end def account_silencing_filter(account) diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb index a2f273281..dd383eb81 100644 --- a/app/models/stream_entry.rb +++ b/app/models/stream_entry.rb @@ -27,7 +27,7 @@ class StreamEntry < ApplicationRecord scope :recent, -> { reorder(id: :desc) } scope :with_includes, -> { includes(:account, status: STATUS_INCLUDES) } - delegate :target, :title, :content, :thread, + delegate :target, :title, :content, :thread, :local_only?, to: :status, allow_nil: true diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index 6addc8a8a..0961ec3e2 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -12,6 +12,8 @@ class StatusPolicy < ApplicationPolicy end def show? + return false if local_only? && (current_account.nil? || !current_account.local?) + if direct? owned? || mention_exists? elsif private? @@ -84,4 +86,8 @@ class StatusPolicy < ApplicationPolicy def author record.account end + + def local_only? + record.local_only? + end end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 300eae547..52d49a69e 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -39,9 +39,12 @@ class PostStatusService < BaseService LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text? DistributionWorker.perform_async(status.id) - Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id) - ActivityPub::DistributionWorker.perform_async(status.id) - ActivityPub::ReplyDistributionWorker.perform_async(status.id) if status.reply? && status.thread.account.local? + + unless status.local_only? + Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id) + ActivityPub::DistributionWorker.perform_async(status.id) + ActivityPub::ReplyDistributionWorker.perform_async(status.id) if status.reply? && status.thread.account.local? + end if options[:idempotency].present? redis.setex("idempotency:status:#{account.id}:#{options[:idempotency]}", 3_600, status.id) diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index 33ddef8b8..03db27406 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -20,8 +20,11 @@ class ReblogService < BaseService reblog = account.statuses.create!(reblog: reblogged_status, text: '') DistributionWorker.perform_async(reblog.id) - Pubsubhubbub::DistributionWorker.perform_async(reblog.stream_entry.id) - ActivityPub::DistributionWorker.perform_async(reblog.id) + + unless reblogged_status.local_only? + Pubsubhubbub::DistributionWorker.perform_async(reblog.stream_entry.id) + ActivityPub::DistributionWorker.perform_async(reblog.id) + end create_notification(reblog) bump_potential_friendship(account, reblog) diff --git a/db/migrate/20171210213213_add_local_only_flag_to_statuses.rb b/db/migrate/20171210213213_add_local_only_flag_to_statuses.rb new file mode 100644 index 000000000..af1e29d6a --- /dev/null +++ b/db/migrate/20171210213213_add_local_only_flag_to_statuses.rb @@ -0,0 +1,5 @@ +class AddLocalOnlyFlagToStatuses < ActiveRecord::Migration[5.1] + def change + add_column :statuses, :local_only, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index f3b06f7c0..10633c1b8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -492,6 +492,7 @@ ActiveRecord::Schema.define(version: 2018_08_20_232245) do t.bigint "account_id", null: false t.bigint "application_id" t.bigint "in_reply_to_account_id" + t.boolean "local_only" t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20180106", order: { id: :desc } t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id" t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id" diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 9d8670129..cf48348f5 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -574,6 +574,32 @@ RSpec.describe Status, type: :model do end end end + + context 'with local-only statuses' do + let(:status) { Fabricate(:status, local_only: true) } + + subject { Status.as_public_timeline(viewer) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'excludes local-only statuses' do + expect(subject).to_not include(status) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'includes local-only statuses' do + expect(subject).to include(status) + end + end + + # TODO: What happens if the viewer is remote? + # Can the viewer be remote? + # What prevents the viewer from being remote? + end end describe '.as_tag_timeline' do @@ -595,6 +621,27 @@ RSpec.describe Status, type: :model do results = Status.as_tag_timeline(tag) expect(results).to include(status) end + + context 'on a local-only status' do + let(:tag) { Fabricate(:tag) } + let(:status) { Fabricate(:status, local_only: true, tags: [tag]) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'filters the local-only status out of the result set' do + expect(Status.as_tag_timeline(tag, viewer)).not_to include(status) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer', domain: nil) } + + it 'keeps the local-only status in the result set' do + expect(Status.as_tag_timeline(tag, viewer)).to include(status) + end + end + end end describe '.permitted_for' do diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb index bacb8fd9e..837fa9cee 100644 --- a/spec/policies/status_policy_spec.rb +++ b/spec/policies/status_policy_spec.rb @@ -71,6 +71,18 @@ RSpec.describe StatusPolicy, type: :model do expect(subject).to_not permit(viewer, status) end + + it 'denies access when local-only and the viewer is not logged in' do + allow(status).to receive(:local_only?) { true } + + expect(subject).to_not permit(nil, status) + end + + it 'denies access when local-only and the viewer is from another domain' do + viewer = Fabricate(:account, domain: 'remote-domain') + allow(status).to receive(:local_only?) { true } + expect(subject).to_not permit(viewer, status) + end end permissions :reblog? do