delete_account_service.rb 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. # frozen_string_literal: true
  2. class DeleteAccountService < BaseService
  3. include Payloadable
  4. ASSOCIATIONS_ON_SUSPEND = %w(
  5. account_notes
  6. account_pins
  7. active_relationships
  8. aliases
  9. block_relationships
  10. blocked_by_relationships
  11. conversation_mutes
  12. conversations
  13. custom_filters
  14. devices
  15. domain_blocks
  16. featured_tags
  17. follow_requests
  18. list_accounts
  19. migrations
  20. mute_relationships
  21. muted_by_relationships
  22. notifications
  23. owned_lists
  24. passive_relationships
  25. report_notes
  26. scheduled_statuses
  27. status_pins
  28. ).freeze
  29. # The following associations have no important side-effects
  30. # in callbacks and all of their own associations are secured
  31. # by foreign keys, making them safe to delete without loading
  32. # into memory
  33. ASSOCIATIONS_WITHOUT_SIDE_EFFECTS = %w(
  34. account_notes
  35. account_pins
  36. aliases
  37. conversation_mutes
  38. conversations
  39. custom_filters
  40. devices
  41. domain_blocks
  42. featured_tags
  43. follow_requests
  44. list_accounts
  45. migrations
  46. mute_relationships
  47. muted_by_relationships
  48. notifications
  49. owned_lists
  50. scheduled_statuses
  51. status_pins
  52. )
  53. ASSOCIATIONS_ON_DESTROY = %w(
  54. reports
  55. targeted_moderation_notes
  56. targeted_reports
  57. ).freeze
  58. # Suspend or remove an account and remove as much of its data
  59. # as possible. If it's a local account and it has not been confirmed
  60. # or never been approved, then side effects are skipped and both
  61. # the user and account records are removed fully. Otherwise,
  62. # it is controlled by options.
  63. # @param [Account]
  64. # @param [Hash] options
  65. # @option [Boolean] :reserve_email Keep user record. Only applicable for local accounts
  66. # @option [Boolean] :reserve_username Keep account record
  67. # @option [Boolean] :skip_side_effects Side effects are ActivityPub and streaming API payloads
  68. # @option [Boolean] :skip_activitypub Skip sending ActivityPub payloads. Implied by :skip_side_effects
  69. # @option [Time] :suspended_at Only applicable when :reserve_username is true
  70. def call(account, **options)
  71. @account = account
  72. @options = { reserve_username: true, reserve_email: true }.merge(options)
  73. if @account.local? && @account.user_unconfirmed_or_pending?
  74. @options[:reserve_email] = false
  75. @options[:reserve_username] = false
  76. @options[:skip_side_effects] = true
  77. end
  78. @options[:skip_activitypub] = true if @options[:skip_side_effects]
  79. distribute_activities!
  80. purge_content!
  81. fulfill_deletion_request!
  82. end
  83. private
  84. def distribute_activities!
  85. return if skip_activitypub?
  86. if @account.local?
  87. delete_actor!
  88. elsif @account.activitypub?
  89. reject_follows!
  90. undo_follows!
  91. end
  92. end
  93. def reject_follows!
  94. # When deleting a remote account, the account obviously doesn't
  95. # actually become deleted on its origin server, i.e. unlike a
  96. # locally deleted account it continues to have access to its home
  97. # feed and other content. To prevent it from being able to continue
  98. # to access toots it would receive because it follows local accounts,
  99. # we have to force it to unfollow them.
  100. ActivityPub::DeliveryWorker.push_bulk(Follow.where(account: @account)) do |follow|
  101. [Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer)), follow.target_account_id, @account.inbox_url]
  102. end
  103. end
  104. def undo_follows!
  105. # When deleting a remote account, the account obviously doesn't
  106. # actually become deleted on its origin server, but following relationships
  107. # are severed on our end. Therefore, make the remote server aware that the
  108. # follow relationships are severed to avoid confusion and potential issues
  109. # if the remote account gets un-suspended.
  110. ActivityPub::DeliveryWorker.push_bulk(Follow.where(target_account: @account)) do |follow|
  111. [Oj.dump(serialize_payload(follow, ActivityPub::UndoFollowSerializer)), follow.account_id, @account.inbox_url]
  112. end
  113. end
  114. def purge_user!
  115. return if !@account.local? || @account.user.nil?
  116. if keep_user_record?
  117. @account.user.disable!
  118. @account.user.invites.where(uses: 0).destroy_all
  119. else
  120. @account.user.destroy
  121. end
  122. end
  123. def purge_content!
  124. purge_user!
  125. purge_profile!
  126. purge_statuses!
  127. purge_mentions!
  128. purge_media_attachments!
  129. purge_polls!
  130. purge_generated_notifications!
  131. purge_favourites!
  132. purge_bookmarks!
  133. purge_feeds!
  134. purge_other_associations!
  135. @account.destroy unless keep_account_record?
  136. end
  137. def purge_statuses!
  138. @account.statuses.reorder(nil).where.not(id: reported_status_ids).in_batches do |statuses|
  139. BatchedRemoveStatusService.new.call(statuses, skip_side_effects: skip_side_effects?)
  140. end
  141. end
  142. def purge_mentions!
  143. @account.mentions.reorder(nil).where.not(status_id: reported_status_ids).in_batches.delete_all
  144. end
  145. def purge_media_attachments!
  146. @account.media_attachments.reorder(nil).find_each do |media_attachment|
  147. next if keep_account_record? && reported_status_ids.include?(media_attachment.status_id)
  148. media_attachment.destroy
  149. end
  150. end
  151. def purge_polls!
  152. @account.polls.reorder(nil).where.not(status_id: reported_status_ids).in_batches.delete_all
  153. end
  154. def purge_generated_notifications!
  155. # By deleting polls and statuses without callbacks, we've left behind
  156. # polymorphically associated notifications generated by this account
  157. Notification.where(from_account: @account).in_batches.delete_all
  158. end
  159. def purge_favourites!
  160. @account.favourites.in_batches do |favourites|
  161. ids = favourites.pluck(:status_id)
  162. StatusStat.where(status_id: ids).update_all('favourites_count = GREATEST(0, favourites_count - 1)')
  163. Chewy.strategy.current.update(StatusesIndex, ids) if Chewy.enabled?
  164. Rails.cache.delete_multi(ids.map { |id| "statuses/#{id}" })
  165. favourites.delete_all
  166. end
  167. end
  168. def purge_bookmarks!
  169. @account.bookmarks.in_batches do |bookmarks|
  170. Chewy.strategy.current.update(StatusesIndex, bookmarks.pluck(:status_id)) if Chewy.enabled?
  171. bookmarks.delete_all
  172. end
  173. end
  174. def purge_other_associations!
  175. associations_for_destruction.each do |association_name|
  176. purge_association(association_name)
  177. end
  178. end
  179. def purge_feeds!
  180. return unless @account.local?
  181. FeedManager.instance.clean_feeds!(:home, [@account.id])
  182. FeedManager.instance.clean_feeds!(:list, @account.owned_lists.pluck(:id))
  183. end
  184. def purge_profile!
  185. # If the account is going to be destroyed
  186. # there is no point wasting time updating
  187. # its values first
  188. return unless keep_account_record?
  189. @account.silenced_at = nil
  190. @account.suspended_at = @options[:suspended_at] || Time.now.utc
  191. @account.suspension_origin = :local
  192. @account.locked = false
  193. @account.memorial = false
  194. @account.discoverable = false
  195. @account.trendable = false
  196. @account.display_name = ''
  197. @account.note = ''
  198. @account.fields = []
  199. @account.statuses_count = 0
  200. @account.followers_count = 0
  201. @account.following_count = 0
  202. @account.moved_to_account = nil
  203. @account.reviewed_at = nil
  204. @account.requested_review_at = nil
  205. @account.also_known_as = []
  206. @account.avatar.destroy
  207. @account.header.destroy
  208. @account.save!
  209. end
  210. def fulfill_deletion_request!
  211. @account.deletion_request&.destroy
  212. end
  213. def purge_association(association_name)
  214. association = @account.public_send(association_name)
  215. if ASSOCIATIONS_WITHOUT_SIDE_EFFECTS.include?(association_name)
  216. association.in_batches.delete_all
  217. else
  218. association.in_batches.destroy_all
  219. end
  220. end
  221. def delete_actor!
  222. ActivityPub::DeliveryWorker.push_bulk(delivery_inboxes) do |inbox_url|
  223. [delete_actor_json, @account.id, inbox_url]
  224. end
  225. ActivityPub::LowPriorityDeliveryWorker.push_bulk(low_priority_delivery_inboxes) do |inbox_url|
  226. [delete_actor_json, @account.id, inbox_url]
  227. end
  228. end
  229. def delete_actor_json
  230. @delete_actor_json ||= Oj.dump(serialize_payload(@account, ActivityPub::DeleteActorSerializer, signer: @account, always_sign: true))
  231. end
  232. def delivery_inboxes
  233. @delivery_inboxes ||= @account.followers.inboxes + Relay.enabled.pluck(:inbox_url)
  234. end
  235. def low_priority_delivery_inboxes
  236. Account.inboxes - delivery_inboxes
  237. end
  238. def reported_status_ids
  239. @reported_status_ids ||= Report.where(target_account: @account).unresolved.pluck(:status_ids).flatten.uniq
  240. end
  241. def associations_for_destruction
  242. if keep_account_record?
  243. ASSOCIATIONS_ON_SUSPEND
  244. else
  245. ASSOCIATIONS_ON_SUSPEND + ASSOCIATIONS_ON_DESTROY
  246. end
  247. end
  248. def keep_user_record?
  249. @options[:reserve_email]
  250. end
  251. def keep_account_record?
  252. @options[:reserve_username]
  253. end
  254. def skip_side_effects?
  255. @options[:skip_side_effects]
  256. end
  257. def skip_activitypub?
  258. @options[:skip_activitypub]
  259. end
  260. end