Ver a proveniência

Improve support for aspects/circles (#8950)

* Add silent column to mentions

* Save silent mentions in ActivityPub Create handler and optimize it

Move networking calls out of the database transaction

* Add "limited" visibility level masked as "private" in the API

Unlike DMs, limited statuses are pushed into home feeds. The access
control rules between direct and limited statuses is almost the same,
except for counter and conversation logic

* Ensure silent column is non-null, add spec

* Ensure filters don't check silent mentions for blocks/mutes

As those are "this person is also allowed to see" rather than "this
person is involved", therefore does not warrant filtering

* Clean up code

* Use Status#active_mentions to limit returned mentions

* Fix code style issues

* Use Status#active_mentions in Notification

And remove stream_entry eager-loading from Notification
master
Eugen Rochko há 5 anos
committed by GitHub
ascendente
cometimento
ddd30f331c
Não foi encontrada uma chave conhecida para esta assinatura, na base de dados ID da chave GPG: 4AEE18F83AFDEB23
23 ficheiros alterados com 142 adições e 35 eliminações
  1. +1
    -1
      app/lib/activitypub/activity.rb
  2. +23
    -1
      app/lib/activitypub/activity/create.rb
  3. +3
    -3
      app/lib/activitypub/tag_manager.rb
  4. +4
    -4
      app/lib/feed_manager.rb
  5. +1
    -1
      app/lib/formatter.rb
  6. +1
    -1
      app/lib/ostatus/atom_serializer.rb
  7. +1
    -1
      app/models/account_conversation.rb
  8. +8
    -0
      app/models/mention.rb
  9. +1
    -1
      app/models/notification.rb
  10. +10
    -5
      app/models/status.rb
  11. +1
    -1
      app/models/stream_entry.rb
  12. +4
    -4
      app/policies/status_policy.rb
  13. +1
    -1
      app/serializers/activitypub/note_serializer.rb
  14. +12
    -1
      app/serializers/rest/status_serializer.rb
  15. +1
    -1
      app/services/batched_remove_status_service.rb
  16. +12
    -0
      app/services/fan_out_on_write_service.rb
  17. +1
    -1
      app/services/remove_status_service.rb
  18. +1
    -1
      app/views/stream_entries/_detailed_status.html.haml
  19. +1
    -1
      app/workers/activitypub/distribution_worker.rb
  20. +1
    -5
      app/workers/activitypub/reply_distribution_worker.rb
  21. +23
    -0
      db/migrate/20181010141500_add_silent_to_mentions.rb
  22. +2
    -1
      db/schema.rb
  23. +29
    -0
      spec/lib/activitypub/activity/create_spec.rb

+ 1
- 1
app/lib/activitypub/activity.rb Ver ficheiro

@@ -96,7 +96,7 @@ class ActivityPub::Activity
end

def notify_about_mentions(status)
status.mentions.includes(:account).each do |mention|
status.active_mentions.includes(:account).each do |mention|
next unless mention.account.local? && audience_includes?(mention.account)
NotifyService.new.call(mention.account, mention)
end


+ 23
- 1
app/lib/activitypub/activity/create.rb Ver ficheiro

@@ -28,6 +28,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity

process_status_params
process_tags
process_audience

ApplicationRecord.transaction do
@status = Status.create!(@params)
@@ -66,6 +67,27 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end
end

def process_audience
(as_array(@object['to']) + as_array(@object['cc'])).uniq.each do |audience|
next if audience == ActivityPub::TagManager::COLLECTIONS[:public]

# Unlike with tags, there is no point in resolving accounts we don't already
# know here, because silent mentions would only be used for local access
# control anyway
account = account_from_uri(audience)

next if account.nil? || @mentions.any? { |mention| mention.account_id == account.id }

@mentions << Mention.new(account: account, silent: true)

# If there is at least one silent mention, then the status can be considered
# as a limited-audience status, and not strictly a direct message
next unless @params[:visibility] == :direct

@params[:visibility] = :limited
end
end

def attach_tags(status)
@tags.each do |tag|
status.tags << tag
@@ -113,7 +135,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity

return if account.nil?

@mentions << Mention.new(account: account)
@mentions << Mention.new(account: account, silent: false)
end

def process_emoji(tag)


+ 3
- 3
app/lib/activitypub/tag_manager.rb Ver ficheiro

@@ -58,8 +58,8 @@ class ActivityPub::TagManager
[COLLECTIONS[:public]]
when 'unlisted', 'private'
[account_followers_url(status.account)]
when 'direct'
status.mentions.map { |mention| uri_for(mention.account) }
when 'direct', 'limited'
status.active_mentions.map { |mention| uri_for(mention.account) }
end
end

@@ -80,7 +80,7 @@ class ActivityPub::TagManager
cc << COLLECTIONS[:public]
end

cc.concat(status.mentions.map { |mention| uri_for(mention.account) }) unless status.direct_visibility?
cc.concat(status.active_mentions.map { |mention| uri_for(mention.account) }) unless status.direct_visibility? || status.limited_visibility?

cc
end


+ 4
- 4
app/lib/feed_manager.rb Ver ficheiro

@@ -88,7 +88,7 @@ class FeedManager
end

query.each do |status|
next if status.direct_visibility? || filter?(:home, status, into_account)
next if status.direct_visibility? || status.limited_visibility? || filter?(:home, status, into_account)
add_to_feed(:home, into_account.id, status)
end

@@ -156,12 +156,12 @@ class FeedManager
return true if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
return true if phrase_filtered?(status, receiver_id, :home)

check_for_blocks = status.mentions.pluck(:account_id)
check_for_blocks = status.active_mentions.pluck(:account_id)
check_for_blocks.concat([status.account_id])

if status.reblog?
check_for_blocks.concat([status.reblog.account_id])
check_for_blocks.concat(status.reblog.mentions.pluck(:account_id))
check_for_blocks.concat(status.reblog.active_mentions.pluck(:account_id))
end

return true if blocks_or_mutes?(receiver_id, check_for_blocks, :home)
@@ -188,7 +188,7 @@ class FeedManager
# This filter is called from NotifyService, but already after the sender of
# the notification has been checked for mute/block. Therefore, it's not
# necessary to check the author of the toot for mute/block again
check_for_blocks = status.mentions.pluck(:account_id)
check_for_blocks = status.active_mentions.pluck(:account_id)
check_for_blocks.concat([status.in_reply_to_account]) if status.reply? && !status.in_reply_to_account_id.nil?

should_filter = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted)


+ 1
- 1
app/lib/formatter.rb Ver ficheiro

@@ -27,7 +27,7 @@ class Formatter
return html.html_safe # rubocop:disable Rails/OutputSafety
end

linkable_accounts = status.mentions.map(&:account)
linkable_accounts = status.active_mentions.map(&:account)
linkable_accounts << status.account

html = raw_content


+ 1
- 1
app/lib/ostatus/atom_serializer.rb Ver ficheiro

@@ -354,7 +354,7 @@ class OStatus::AtomSerializer
append_element(entry, 'summary', status.spoiler_text, 'xml:lang': status.language) if status.spoiler_text?
append_element(entry, 'content', Formatter.instance.format(status).to_str || '.', type: 'html', 'xml:lang': status.language)

status.mentions.sort_by(&:id).each do |mentioned|
status.active_mentions.sort_by(&:id).each do |mentioned|
append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': OStatus::TagManager::TYPES[:person], href: OStatus::TagManager.instance.uri_for(mentioned.account))
end



+ 1
- 1
app/models/account_conversation.rb Ver ficheiro

@@ -85,7 +85,7 @@ class AccountConversation < ApplicationRecord
private

def participants_from_status(recipient, status)
((status.mentions.pluck(:account_id) + [status.account_id]).uniq - [recipient.id]).sort
((status.active_mentions.pluck(:account_id) + [status.account_id]).uniq - [recipient.id]).sort
end
end



+ 8
- 0
app/models/mention.rb Ver ficheiro

@@ -8,6 +8,7 @@
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint(8)
# silent :boolean default(FALSE), not null
#

class Mention < ApplicationRecord
@@ -18,10 +19,17 @@ class Mention < ApplicationRecord

validates :account, uniqueness: { scope: :status }

scope :active, -> { where(silent: false) }
scope :silent, -> { where(silent: true) }

delegate(
:username,
:acct,
to: :account,
prefix: true
)

def active?
!silent?
end
end

+ 1
- 1
app/models/notification.rb Ver ficheiro

@@ -24,7 +24,7 @@ class Notification < ApplicationRecord
favourite: 'Favourite',
}.freeze

STATUS_INCLUDES = [:account, :application, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :application, :media_attachments, :tags, mentions: :account]].freeze
STATUS_INCLUDES = [:account, :application, :media_attachments, :tags, active_mentions: :account, reblog: [:account, :application, :media_attachments, :tags, active_mentions: :account]].freeze

belongs_to :account, optional: true
belongs_to :from_account, class_name: 'Account', optional: true


+ 10
- 5
app/models/status.rb Ver ficheiro

@@ -37,7 +37,7 @@ class Status < ApplicationRecord

update_index('statuses#status', :proper) if Chewy.enabled?

enum visibility: [:public, :unlisted, :private, :direct], _suffix: :visibility
enum visibility: [:public, :unlisted, :private, :direct, :limited], _suffix: :visibility

belongs_to :application, class_name: 'Doorkeeper::Application', optional: true

@@ -51,7 +51,8 @@ class Status < ApplicationRecord
has_many :favourites, inverse_of: :status, dependent: :destroy
has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy
has_many :replies, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :thread
has_many :mentions, dependent: :destroy
has_many :mentions, dependent: :destroy, inverse_of: :status
has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status
has_many :media_attachments, dependent: :nullify

has_and_belongs_to_many :tags
@@ -89,7 +90,7 @@ class Status < ApplicationRecord
:status_stat,
:tags,
:stream_entry,
mentions: :account,
active_mentions: :account,
reblog: [
:account,
:application,
@@ -98,7 +99,7 @@ class Status < ApplicationRecord
:media_attachments,
:conversation,
:status_stat,
mentions: :account,
active_mentions: :account,
],
thread: :account

@@ -171,7 +172,11 @@ class Status < ApplicationRecord
end

def hidden?
private_visibility? || direct_visibility?
private_visibility? || direct_visibility? || limited_visibility?
end

def distributable?
public_visibility? || unlisted_visibility?
end

def with_media?


+ 1
- 1
app/models/stream_entry.rb Ver ficheiro

@@ -48,7 +48,7 @@ class StreamEntry < ApplicationRecord
end

def mentions
orphaned? ? [] : status.mentions.map(&:account)
orphaned? ? [] : status.active_mentions.map(&:account)
end

private


+ 4
- 4
app/policies/status_policy.rb Ver ficheiro

@@ -12,7 +12,7 @@ class StatusPolicy < ApplicationPolicy
end

def show?
if direct?
if requires_mention?
owned? || mention_exists?
elsif private?
owned? || following_author? || mention_exists?
@@ -22,7 +22,7 @@ class StatusPolicy < ApplicationPolicy
end

def reblog?
!direct? && (!private? || owned?) && show? && !blocking_author?
!requires_mention? && (!private? || owned?) && show? && !blocking_author?
end

def favourite?
@@ -41,8 +41,8 @@ class StatusPolicy < ApplicationPolicy

private

def direct?
record.direct_visibility?
def requires_mention?
record.direct_visibility? || record.limited_visibility?
end

def owned?


+ 1
- 1
app/serializers/activitypub/note_serializer.rb Ver ficheiro

@@ -68,7 +68,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
end

def virtual_tags
object.mentions.to_a.sort_by(&:id) + object.tags + object.emojis
object.active_mentions.to_a.sort_by(&:id) + object.tags + object.emojis
end

def atom_uri


+ 12
- 1
app/serializers/rest/status_serializer.rb Ver ficheiro

@@ -36,6 +36,17 @@ class REST::StatusSerializer < ActiveModel::Serializer
!current_user.nil?
end

def visibility
# This visibility is masked behind "private"
# to avoid API changes because there are no
# UX differences
if object.limited_visibility?
'private'
else
object.visibility
end
end

def uri
OStatus::TagManager.instance.uri_for(object)
end
@@ -88,7 +99,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
end

def ordered_mentions
object.mentions.to_a.sort_by(&:id)
object.active_mentions.to_a.sort_by(&:id)
end

class ApplicationSerializer < ActiveModel::Serializer


+ 1
- 1
app/services/batched_remove_status_service.rb Ver ficheiro

@@ -12,7 +12,7 @@ class BatchedRemoveStatusService < BaseService
def call(statuses)
statuses = Status.where(id: statuses.map(&:id)).includes(:account, :stream_entry).flat_map { |status| [status] + status.reblogs.includes(:account, :stream_entry).to_a }

@mentions = statuses.map { |s| [s.id, s.mentions.includes(:account).to_a] }.to_h
@mentions = statuses.map { |s| [s.id, s.active_mentions.includes(:account).to_a] }.to_h
@tags = statuses.map { |s| [s.id, s.tags.pluck(:name)] }.to_h

@stream_entry_batches = []


+ 12
- 0
app/services/fan_out_on_write_service.rb Ver ficheiro

@@ -10,6 +10,8 @@ class FanOutOnWriteService < BaseService

if status.direct_visibility?
deliver_to_own_conversation(status)
elsif status.limited_visibility?
deliver_to_mentioned_followers(status)
else
deliver_to_self(status) if status.account.local?
deliver_to_followers(status)
@@ -53,6 +55,16 @@ class FanOutOnWriteService < BaseService
end
end

def deliver_to_mentioned_followers(status)
Rails.logger.debug "Delivering status #{status.id} to limited followers"

status.mentions.includes(:account).each do |mention|
mentioned_account = mention.account
next if !mentioned_account.local? || !mentioned_account.following?(status.account) || FeedManager.instance.filter?(:home, status, mention.account_id)
FeedManager.instance.push_to_home(mentioned_account, status)
end
end

def render_anonymous_payload(status)
@payload = InlineRenderer.render(status, nil, :status)
@payload = Oj.dump(event: :update, payload: @payload)


+ 1
- 1
app/services/remove_status_service.rb Ver ficheiro

@@ -8,7 +8,7 @@ class RemoveStatusService < BaseService
@status = status
@account = status.account
@tags = status.tags.pluck(:name).to_a
@mentions = status.mentions.includes(:account).to_a
@mentions = status.active_mentions.includes(:account).to_a
@reblogs = status.reblogs.to_a
@stream_entry = status.stream_entry
@options = options


+ 1
- 1
app/views/stream_entries/_detailed_status.html.haml Ver ficheiro

@@ -51,7 +51,7 @@
- if status.direct_visibility?
%span.detailed-status__link<
= fa_icon('envelope')
- elsif status.private_visibility?
- elsif status.private_visibility? || status.limited_visibility?
%span.detailed-status__link<
= fa_icon('lock')
- else


+ 1
- 1
app/workers/activitypub/distribution_worker.rb Ver ficheiro

@@ -23,7 +23,7 @@ class ActivityPub::DistributionWorker
private

def skip_distribution?
@status.direct_visibility?
@status.direct_visibility? || @status.limited_visibility?
end

def relayable?


+ 1
- 5
app/workers/activitypub/reply_distribution_worker.rb Ver ficheiro

@@ -9,7 +9,7 @@ class ActivityPub::ReplyDistributionWorker
@status = Status.find(status_id)
@account = @status.thread&.account

return if @account.nil? || skip_distribution?
return unless @account.present? && @status.distributable?

ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
[signed_payload, @status.account_id, inbox_url]
@@ -20,10 +20,6 @@ class ActivityPub::ReplyDistributionWorker

private

def skip_distribution?
@status.private_visibility? || @status.direct_visibility?
end

def inboxes
@inboxes ||= @account.followers.inboxes
end


+ 23
- 0
db/migrate/20181010141500_add_silent_to_mentions.rb Ver ficheiro

@@ -0,0 +1,23 @@
require Rails.root.join('lib', 'mastodon', 'migration_helpers')

class AddSilentToMentions < ActiveRecord::Migration[5.2]
include Mastodon::MigrationHelpers

disable_ddl_transaction!

def up
safety_assured do
add_column_with_default(
:mentions,
:silent,
:boolean,
allow_null: false,
default: false
)
end
end

def down
remove_column :mentions, :silent
end
end

+ 2
- 1
db/schema.rb Ver ficheiro

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2018_10_07_025445) do
ActiveRecord::Schema.define(version: 2018_10_10_141500) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -301,6 +301,7 @@ ActiveRecord::Schema.define(version: 2018_10_07_025445) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "account_id"
t.boolean "silent", default: false, null: false
t.index ["account_id", "status_id"], name: "index_mentions_on_account_id_and_status_id", unique: true
t.index ["status_id"], name: "index_mentions_on_status_id"
end


+ 29
- 0
spec/lib/activitypub/activity/create_spec.rb Ver ficheiro

@@ -105,6 +105,31 @@ RSpec.describe ActivityPub::Activity::Create do
end
end

context 'limited' do
let(:recipient) { Fabricate(:account) }

let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
to: ActivityPub::TagManager.instance.uri_for(recipient),
}
end

it 'creates status' do
status = sender.statuses.first

expect(status).to_not be_nil
expect(status.visibility).to eq 'limited'
end

it 'creates silent mention' do
status = sender.statuses.first
expect(status.mentions.first).to be_silent
end
end

context 'direct' do
let(:recipient) { Fabricate(:account) }

@@ -114,6 +139,10 @@ RSpec.describe ActivityPub::Activity::Create do
type: 'Note',
content: 'Lorem ipsum',
to: ActivityPub::TagManager.instance.uri_for(recipient),
tag: {
type: 'Mention',
href: ActivityPub::TagManager.instance.uri_for(recipient),
},
}
end



Carregando…
Cancelar
Guardar