resolve_account_service.rb 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. # frozen_string_literal: true
  2. class ResolveAccountService < BaseService
  3. include OStatus2::MagicKey
  4. include JsonLdHelper
  5. DFRN_NS = 'http://purl.org/macgirvin/dfrn/1.0'
  6. # Find or create a local account for a remote user.
  7. # When creating, look up the user's webfinger and fetch all
  8. # important information from their feed
  9. # @param [String] uri User URI in the form of username@domain
  10. # @return [Account]
  11. def call(uri, update_profile = true, redirected = nil)
  12. @username, @domain = uri.split('@')
  13. @update_profile = update_profile
  14. return Account.find_local(@username) if TagManager.instance.local_domain?(@domain)
  15. @account = Account.find_remote(@username, @domain)
  16. return @account unless webfinger_update_due?
  17. Rails.logger.debug "Looking up webfinger for #{uri}"
  18. @webfinger = Goldfinger.finger("acct:#{uri}")
  19. confirmed_username, confirmed_domain = @webfinger.subject.gsub(/\Aacct:/, '').split('@')
  20. if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
  21. @username = confirmed_username
  22. @domain = confirmed_domain
  23. elsif redirected.nil?
  24. return call("#{confirmed_username}@#{confirmed_domain}", update_profile, true)
  25. else
  26. Rails.logger.debug 'Requested and returned acct URIs do not match'
  27. return
  28. end
  29. return if links_missing?
  30. return Account.find_local(@username) if TagManager.instance.local_domain?(@domain)
  31. RedisLock.acquire(lock_options) do |lock|
  32. if lock.acquired?
  33. @account = Account.find_remote(@username, @domain)
  34. if activitypub_ready? || @account&.activitypub?
  35. handle_activitypub
  36. else
  37. handle_ostatus
  38. end
  39. end
  40. end
  41. @account
  42. rescue Goldfinger::Error => e
  43. Rails.logger.debug "Webfinger query for #{uri} unsuccessful: #{e}"
  44. nil
  45. end
  46. private
  47. def links_missing?
  48. !(activitypub_ready? || ostatus_ready?)
  49. end
  50. def ostatus_ready?
  51. !(@webfinger.link('http://schemas.google.com/g/2010#updates-from').nil? ||
  52. @webfinger.link('salmon').nil? ||
  53. @webfinger.link('http://webfinger.net/rel/profile-page').nil? ||
  54. @webfinger.link('magic-public-key').nil? ||
  55. canonical_uri.nil? ||
  56. hub_url.nil?)
  57. end
  58. def webfinger_update_due?
  59. @account.nil? || @account.possibly_stale?
  60. end
  61. def activitypub_ready?
  62. !@webfinger.link('self').nil? &&
  63. ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self').type) &&
  64. !actor_json.nil? &&
  65. actor_json['inbox'].present?
  66. end
  67. def handle_ostatus
  68. create_account if @account.nil?
  69. update_account
  70. update_account_profile if update_profile?
  71. end
  72. def update_profile?
  73. @update_profile
  74. end
  75. def handle_activitypub
  76. return if actor_json.nil?
  77. @account = ActivityPub::ProcessAccountService.new.call(@username, @domain, actor_json)
  78. rescue Oj::ParseError
  79. nil
  80. end
  81. def create_account
  82. Rails.logger.debug "Creating new remote account for #{@username}@#{@domain}"
  83. @account = Account.new(username: @username, domain: @domain)
  84. @account.suspended = true if auto_suspend?
  85. @account.silenced = true if auto_silence?
  86. @account.private_key = nil
  87. end
  88. def update_account
  89. @account.last_webfingered_at = Time.now.utc
  90. @account.protocol = :ostatus
  91. @account.remote_url = atom_url
  92. @account.salmon_url = salmon_url
  93. @account.url = url
  94. @account.public_key = public_key
  95. @account.uri = canonical_uri
  96. @account.hub_url = hub_url
  97. @account.save!
  98. end
  99. def auto_suspend?
  100. domain_block&.suspend?
  101. end
  102. def auto_silence?
  103. domain_block&.silence?
  104. end
  105. def domain_block
  106. return @domain_block if defined?(@domain_block)
  107. @domain_block = DomainBlock.find_by(domain: @domain)
  108. end
  109. def atom_url
  110. @atom_url ||= @webfinger.link('http://schemas.google.com/g/2010#updates-from').href
  111. end
  112. def salmon_url
  113. @salmon_url ||= @webfinger.link('salmon').href
  114. end
  115. def actor_url
  116. @actor_url ||= @webfinger.link('self').href
  117. end
  118. def url
  119. @url ||= @webfinger.link('http://webfinger.net/rel/profile-page').href
  120. end
  121. def public_key
  122. @public_key ||= magic_key_to_pem(@webfinger.link('magic-public-key').href)
  123. end
  124. def canonical_uri
  125. return @canonical_uri if defined?(@canonical_uri)
  126. author_uri = atom.at_xpath('/xmlns:feed/xmlns:author/xmlns:uri')
  127. if author_uri.nil?
  128. owner = atom.at_xpath('/xmlns:feed').at_xpath('./dfrn:owner', dfrn: DFRN_NS)
  129. author_uri = owner.at_xpath('./xmlns:uri') unless owner.nil?
  130. end
  131. @canonical_uri = author_uri.nil? ? nil : author_uri.content
  132. end
  133. def hub_url
  134. return @hub_url if defined?(@hub_url)
  135. hubs = atom.xpath('//xmlns:link[@rel="hub"]')
  136. @hub_url = hubs.empty? || hubs.first['href'].nil? ? nil : hubs.first['href']
  137. end
  138. def atom_body
  139. return @atom_body if defined?(@atom_body)
  140. response = Request.new(:get, atom_url).perform
  141. raise Mastodon::UnexpectedResponseError, response unless response.code == 200
  142. @atom_body = response.to_s
  143. end
  144. def actor_json
  145. return @actor_json if defined?(@actor_json)
  146. json = fetch_resource(actor_url, false)
  147. @actor_json = supported_context?(json) && json['type'] == 'Person' ? json : nil
  148. end
  149. def atom
  150. return @atom if defined?(@atom)
  151. @atom = Nokogiri::XML(atom_body)
  152. end
  153. def update_account_profile
  154. RemoteProfileUpdateWorker.perform_async(@account.id, atom_body.force_encoding('UTF-8'), false)
  155. end
  156. def lock_options
  157. { redis: Redis.current, key: "resolve:#{@username}@#{@domain}" }
  158. end
  159. end