delete_account_service.rb 9.7 KB

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