resolve_remote_account_service.rb 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. # frozen_string_literal: true
  2. class ResolveRemoteAccountService < 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?
  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.last_webfingered_at.nil? || @account.last_webfingered_at <= 1.day.ago
  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['inbox'].present?
  65. end
  66. def handle_ostatus
  67. create_account if @account.nil?
  68. update_account
  69. update_account_profile if update_profile?
  70. end
  71. def update_profile?
  72. @update_profile
  73. end
  74. def handle_activitypub
  75. return if actor_json.nil?
  76. @account = ActivityPub::ProcessAccountService.new.call(@username, @domain, actor_json)
  77. rescue Oj::ParseError
  78. nil
  79. end
  80. def create_account
  81. Rails.logger.debug "Creating new remote account for #{@username}@#{@domain}"
  82. @account = Account.new(username: @username, domain: @domain)
  83. @account.suspended = true if auto_suspend?
  84. @account.silenced = true if auto_silence?
  85. @account.private_key = nil
  86. end
  87. def update_account
  88. @account.last_webfingered_at = Time.now.utc
  89. @account.protocol = :ostatus
  90. @account.remote_url = atom_url
  91. @account.salmon_url = salmon_url
  92. @account.url = url
  93. @account.public_key = public_key
  94. @account.uri = canonical_uri
  95. @account.hub_url = hub_url
  96. @account.save!
  97. end
  98. def auto_suspend?
  99. domain_block && domain_block.suspend?
  100. end
  101. def auto_silence?
  102. domain_block && domain_block.silence?
  103. end
  104. def domain_block
  105. return @domain_block if defined?(@domain_block)
  106. @domain_block = DomainBlock.find_by(domain: @domain)
  107. end
  108. def atom_url
  109. @atom_url ||= @webfinger.link('http://schemas.google.com/g/2010#updates-from').href
  110. end
  111. def salmon_url
  112. @salmon_url ||= @webfinger.link('salmon').href
  113. end
  114. def actor_url
  115. @actor_url ||= @webfinger.link('self').href
  116. end
  117. def url
  118. @url ||= @webfinger.link('http://webfinger.net/rel/profile-page').href
  119. end
  120. def public_key
  121. @public_key ||= magic_key_to_pem(@webfinger.link('magic-public-key').href)
  122. end
  123. def canonical_uri
  124. return @canonical_uri if defined?(@canonical_uri)
  125. author_uri = atom.at_xpath('/xmlns:feed/xmlns:author/xmlns:uri')
  126. if author_uri.nil?
  127. owner = atom.at_xpath('/xmlns:feed').at_xpath('./dfrn:owner', dfrn: DFRN_NS)
  128. author_uri = owner.at_xpath('./xmlns:uri') unless owner.nil?
  129. end
  130. @canonical_uri = author_uri.nil? ? nil : author_uri.content
  131. end
  132. def hub_url
  133. return @hub_url if defined?(@hub_url)
  134. hubs = atom.xpath('//xmlns:link[@rel="hub"]')
  135. @hub_url = hubs.empty? || hubs.first['href'].nil? ? nil : hubs.first['href']
  136. end
  137. def atom_body
  138. return @atom_body if defined?(@atom_body)
  139. response = Request.new(:get, atom_url).perform
  140. raise Mastodon::UnexpectedResponseError, response unless response.code == 200
  141. @atom_body = response.to_s
  142. end
  143. def actor_json
  144. return @actor_json if defined?(@actor_json)
  145. json = fetch_resource(actor_url)
  146. @actor_json = supported_context?(json) && json['type'] == 'Person' ? json : nil
  147. end
  148. def atom
  149. return @atom if defined?(@atom)
  150. @atom = Nokogiri::XML(atom_body)
  151. end
  152. def update_account_profile
  153. RemoteProfileUpdateWorker.perform_async(@account.id, atom_body.force_encoding('UTF-8'), false)
  154. end
  155. def lock_options
  156. { redis: Redis.current, key: "resolve:#{@username}@#{@domain}" }
  157. end
  158. end