about summary refs log tree commit diff
path: root/app/lib/activitypub/activity
diff options
context:
space:
mode:
Diffstat (limited to 'app/lib/activitypub/activity')
-rw-r--r--app/lib/activitypub/activity/accept.rb9
-rw-r--r--app/lib/activitypub/activity/announce.rb2
-rw-r--r--app/lib/activitypub/activity/create.rb101
-rw-r--r--app/lib/activitypub/activity/delete.rb7
-rw-r--r--app/lib/activitypub/activity/flag.rb2
-rw-r--r--app/lib/activitypub/activity/follow.rb4
-rw-r--r--app/lib/activitypub/activity/move.rb17
-rw-r--r--app/lib/activitypub/activity/reject.rb10
8 files changed, 100 insertions, 52 deletions
diff --git a/app/lib/activitypub/activity/accept.rb b/app/lib/activitypub/activity/accept.rb
index 348ee0d1c..7010ff43e 100644
--- a/app/lib/activitypub/activity/accept.rb
+++ b/app/lib/activitypub/activity/accept.rb
@@ -2,17 +2,18 @@
 
 class ActivityPub::Activity::Accept < ActivityPub::Activity
   def perform
+    return accept_follow_for_relay if relay_follow?
+    return follow_request_from_object.authorize! unless follow_request_from_object.nil?
+
     case @object['type']
     when 'Follow'
-      accept_follow
+      accept_embedded_follow
     end
   end
 
   private
 
-  def accept_follow
-    return accept_follow_for_relay if relay_follow?
-
+  def accept_embedded_follow
     target_account = account_from_uri(target_uri)
 
     return if target_account.nil? || !target_account.local?
diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb
index 1aa6ee9ec..34c646668 100644
--- a/app/lib/activitypub/activity/announce.rb
+++ b/app/lib/activitypub/activity/announce.rb
@@ -40,7 +40,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
   end
 
   def announceable?(status)
-    status.account_id == @account.id || status.public_visibility? || status.unlisted_visibility?
+    status.account_id == @account.id || status.distributable?
   end
 
   def related_to_local_activity?
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index f55dd35b2..8a12a2b08 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -25,6 +25,14 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 
   private
 
+  def audience_to
+    @object['to'] || @json['to']
+  end
+
+  def audience_cc
+    @object['cc'] || @json['cc']
+  end
+
   def process_status
     @tags     = []
     @mentions = []
@@ -41,8 +49,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 
     resolve_thread(@status)
     fetch_replies(@status)
+    check_for_spam
     distribute(@status)
-    forward_for_reply if @status.public_visibility? || @status.unlisted_visibility?
+    forward_for_reply if @status.distributable?
   end
 
   def find_existing_status
@@ -74,7 +83,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   end
 
   def process_audience
-    (as_array(@object['to']) + as_array(@object['cc'])).uniq.each do |audience|
+    (as_array(audience_to) + as_array(audience_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
@@ -147,12 +156,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   def process_hashtag(tag)
     return if tag['name'].blank?
 
-    hashtag = tag['name'].gsub(/\A#/, '').mb_chars.downcase
-    hashtag = Tag.where(name: hashtag).first_or_create!(name: hashtag)
-
-    return if @tags.include?(hashtag)
-
-    @tags << hashtag
+    Tag.find_or_create_by_names(tag['name']) do |hashtag|
+      @tags << hashtag unless @tags.include?(hashtag)
+    end
   rescue ActiveRecord::RecordInvalid
     nil
   end
@@ -191,22 +197,25 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     media_attachments = []
 
     as_array(@object['attachment']).each do |attachment|
-      next if attachment['url'].blank?
+      next if attachment['url'].blank? || media_attachments.size >= 4
 
-      href             = Addressable::URI.parse(attachment['url']).normalize.to_s
-      media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'], blurhash: supported_blurhash?(attachment['blurhash']) ? attachment['blurhash'] : nil)
-      media_attachments << media_attachment
+      begin
+        href             = Addressable::URI.parse(attachment['url']).normalize.to_s
+        media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'], blurhash: supported_blurhash?(attachment['blurhash']) ? attachment['blurhash'] : nil)
+        media_attachments << media_attachment
 
-      next if unsupported_media_type?(attachment['mediaType']) || skip_download?
+        next if unsupported_media_type?(attachment['mediaType']) || skip_download?
 
-      media_attachment.file_remote_url = href
-      media_attachment.save
+        media_attachment.file_remote_url = href
+        media_attachment.save
+      rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
+        RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id)
+      end
     end
 
     media_attachments
   rescue Addressable::URI::InvalidURIError => e
-    Rails.logger.debug e
-
+    Rails.logger.debug "Invalid URL in attachment: #{e}"
     media_attachments
   end
 
@@ -231,25 +240,40 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
       items    = @object['oneOf']
     end
 
+    voters_count = @object['votersCount']
+
     @account.polls.new(
       multiple: multiple,
       expires_at: expires_at,
       options: items.map { |item| item['name'].presence || item['content'] }.compact,
-      cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 }
+      cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 },
+      voters_count: voters_count
     )
   end
 
   def poll_vote?
     return false if replied_to_status.nil? || replied_to_status.preloadable_poll.nil? || !replied_to_status.local? || !replied_to_status.preloadable_poll.options.include?(@object['name'])
 
-    unless replied_to_status.preloadable_poll.expired?
-      replied_to_status.preloadable_poll.votes.create!(account: @account, choice: replied_to_status.preloadable_poll.options.index(@object['name']), uri: @object['id'])
-      ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, replied_to_status.id) unless replied_to_status.preloadable_poll.hide_totals?
-    end
+    poll_vote! unless replied_to_status.preloadable_poll.expired?
 
     true
   end
 
+  def poll_vote!
+    poll = replied_to_status.preloadable_poll
+    already_voted = true
+    RedisLock.acquire(poll_lock_options) do |lock|
+      if lock.acquired?
+        already_voted = poll.votes.where(account: @account).exists?
+        poll.votes.create!(account: @account, choice: poll.options.index(@object['name']), uri: @object['id'])
+      else
+        raise Mastodon::RaceConditionError
+      end
+    end
+    increment_voters_count! unless already_voted
+    ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, replied_to_status.id) unless replied_to_status.preloadable_poll.hide_totals?
+  end
+
   def resolve_thread(status)
     return unless status.reply? && status.thread.nil? && Request.valid_url?(in_reply_to_uri)
     ThreadResolveWorker.perform_async(status.id, in_reply_to_uri)
@@ -275,11 +299,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   end
 
   def visibility_from_audience
-    if equals_or_includes?(@object['to'], ActivityPub::TagManager::COLLECTIONS[:public])
+    if equals_or_includes?(audience_to, ActivityPub::TagManager::COLLECTIONS[:public])
       :public
-    elsif equals_or_includes?(@object['cc'], ActivityPub::TagManager::COLLECTIONS[:public])
+    elsif equals_or_includes?(audience_cc, ActivityPub::TagManager::COLLECTIONS[:public])
       :unlisted
-    elsif equals_or_includes?(@object['to'], @account.followers_url)
+    elsif equals_or_includes?(audience_to, @account.followers_url)
       :private
     else
       :direct
@@ -288,7 +312,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 
   def audience_includes?(account)
     uri = ActivityPub::TagManager.instance.uri_for(account)
-    equals_or_includes?(@object['to'], uri) || equals_or_includes?(@object['cc'], uri)
+    equals_or_includes?(audience_to, uri) || equals_or_includes?(audience_cc, uri)
   end
 
   def replied_to_status
@@ -370,7 +394,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   end
 
   def unsupported_media_type?(mime_type)
-    mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
+    mime_type.present? && !MediaAttachment.supported_mime_types.include?(mime_type)
   end
 
   def supported_blurhash?(blurhash)
@@ -380,7 +404,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 
   def skip_download?
     return @skip_download if defined?(@skip_download)
-    @skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
+    @skip_download ||= DomainBlock.reject_media?(@account.domain)
   end
 
   def reply_to_local?
@@ -399,19 +423,38 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   def addresses_local_accounts?
     return true if @options[:delivered_to_account_id]
 
-    local_usernames = (as_array(@object['to']) + as_array(@object['cc'])).uniq.select { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }.map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username) }
+    local_usernames = (as_array(audience_to) + as_array(audience_cc)).uniq.select { |uri| ActivityPub::TagManager.instance.local_uri?(uri) }.map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username) }
 
     return false if local_usernames.empty?
 
     Account.local.where(username: local_usernames).exists?
   end
 
+  def check_for_spam
+    SpamCheck.perform(@status)
+  end
+
   def forward_for_reply
     return unless @json['signature'].present? && reply_to_local?
     ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), replied_to_status.account_id, [@account.preferred_inbox_url])
   end
 
+  def increment_voters_count!
+    poll = replied_to_status.preloadable_poll
+    unless poll.voters_count.nil?
+      poll.voters_count = poll.voters_count + 1
+      poll.save
+    end
+  rescue ActiveRecord::StaleObjectError
+    poll.reload
+    retry
+  end
+
   def lock_options
     { redis: Redis.current, key: "create:#{@object['id']}" }
   end
+
+  def poll_lock_options
+    { redis: Redis.current, key: "vote:#{replied_to_status.poll_id}:#{@account.id}" }
+  end
 end
diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb
index 0eb14b89c..dc9ff580c 100644
--- a/app/lib/activitypub/activity/delete.rb
+++ b/app/lib/activitypub/activity/delete.rb
@@ -13,8 +13,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
 
   def delete_person
     lock_or_return("delete_in_progress:#{@account.id}") do
-      SuspendAccountService.new.call(@account)
-      @account.destroy!
+      SuspendAccountService.new.call(@account, reserve_username: false)
     end
   end
 
@@ -31,7 +30,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
 
     return if @status.nil?
 
-    if @status.public_visibility? || @status.unlisted_visibility?
+    if @status.distributable?
       forward_for_reply
       forward_for_reblogs
     end
@@ -70,7 +69,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
   end
 
   def delete_now!
-    RemoveStatusService.new.call(@status)
+    RemoveStatusService.new.call(@status, redraft: false)
   end
 
   def payload
diff --git a/app/lib/activitypub/activity/flag.rb b/app/lib/activitypub/activity/flag.rb
index f73b93058..1659bc61f 100644
--- a/app/lib/activitypub/activity/flag.rb
+++ b/app/lib/activitypub/activity/flag.rb
@@ -23,7 +23,7 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
   private
 
   def skip_reports?
-    DomainBlock.find_by(domain: @account.domain)&.reject_reports?
+    DomainBlock.reject_reports?(@account.domain)
   end
 
   def object_uris
diff --git a/app/lib/activitypub/activity/follow.rb b/app/lib/activitypub/activity/follow.rb
index 3eb88339a..ec92f4255 100644
--- a/app/lib/activitypub/activity/follow.rb
+++ b/app/lib/activitypub/activity/follow.rb
@@ -8,7 +8,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
 
     return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.requested?(target_account)
 
-    if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved?
+    if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved? || target_account.instance_actor?
       reject_follow_request!(target_account)
       return
     end
@@ -21,7 +21,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
 
     follow_request = FollowRequest.create!(account: @account, target_account: target_account, uri: @json['id'])
 
-    if target_account.locked?
+    if target_account.locked? || @account.silenced?
       NotifyService.new.call(target_account, follow_request)
     else
       AuthorizeFollowService.new.call(@account, target_account)
diff --git a/app/lib/activitypub/activity/move.rb b/app/lib/activitypub/activity/move.rb
index d7a5f595c..12bb82d25 100644
--- a/app/lib/activitypub/activity/move.rb
+++ b/app/lib/activitypub/activity/move.rb
@@ -10,17 +10,16 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
 
     target_account = ActivityPub::FetchRemoteAccountService.new.call(target_uri)
 
-    return if target_account.nil? || !target_account.also_known_as.include?(origin_account.uri)
+    if target_account.nil? || target_account.suspended? || !target_account.also_known_as.include?(origin_account.uri)
+      unmark_as_processing!
+      return
+    end
 
     # In case for some reason we didn't have a redirect for the profile already, set it
-    origin_account.update(moved_to_account: target_account) if origin_account.moved_to_account_id.nil?
+    origin_account.update(moved_to_account: target_account)
 
     # Initiate a re-follow for each follower
-    origin_account.followers.local.select(:id).find_in_batches do |follower_accounts|
-      UnfollowFollowWorker.push_bulk(follower_accounts.map(&:id)) do |follower_account_id|
-        [follower_account_id, origin_account.id, target_account.id]
-      end
-    end
+    MoveWorker.perform_async(origin_account.id, target_account.id)
   end
 
   private
@@ -40,4 +39,8 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
   def mark_as_processing!
     redis.setex("move_in_progress:#{@account.id}", PROCESSING_COOLDOWN, true)
   end
+
+  def unmark_as_processing!
+    redis.del("move_in_progress:#{@account.id}")
+  end
 end
diff --git a/app/lib/activitypub/activity/reject.rb b/app/lib/activitypub/activity/reject.rb
index dba21fb9a..8d771ed81 100644
--- a/app/lib/activitypub/activity/reject.rb
+++ b/app/lib/activitypub/activity/reject.rb
@@ -2,17 +2,19 @@
 
 class ActivityPub::Activity::Reject < ActivityPub::Activity
   def perform
+    return reject_follow_for_relay if relay_follow?
+    return follow_request_from_object.reject! unless follow_request_from_object.nil?
+    return UnfollowService.new.call(follow_from_object.target_account, @account) unless follow_from_object.nil?
+
     case @object['type']
     when 'Follow'
-      reject_follow
+      reject_embedded_follow
     end
   end
 
   private
 
-  def reject_follow
-    return reject_follow_for_relay if relay_follow?
-
+  def reject_embedded_follow
     target_account = account_from_uri(target_uri)
 
     return if target_account.nil? || !target_account.local?