You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
5.8 KiB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
  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. else
  40. raise Mastodon::RaceConditionError
  41. end
  42. end
  43. @account
  44. rescue Goldfinger::Error => e
  45. Rails.logger.debug "Webfinger query for #{uri} unsuccessful: #{e}"
  46. nil
  47. end
  48. private
  49. def links_missing?
  50. !(activitypub_ready? || ostatus_ready?)
  51. end
  52. def ostatus_ready?
  53. !(@webfinger.link('http://schemas.google.com/g/2010#updates-from').nil? ||
  54. @webfinger.link('salmon').nil? ||
  55. @webfinger.link('http://webfinger.net/rel/profile-page').nil? ||
  56. @webfinger.link('magic-public-key').nil? ||
  57. canonical_uri.nil? ||
  58. hub_url.nil?)
  59. end
  60. def webfinger_update_due?
  61. @account.nil? || @account.possibly_stale?
  62. end
  63. def activitypub_ready?
  64. !@webfinger.link('self').nil? &&
  65. ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self').type) &&
  66. !actor_json.nil? &&
  67. actor_json['inbox'].present?
  68. end
  69. def handle_ostatus
  70. create_account if @account.nil?
  71. update_account
  72. update_account_profile if update_profile?
  73. end
  74. def update_profile?
  75. @update_profile
  76. end
  77. def handle_activitypub
  78. return if actor_json.nil?
  79. @account = ActivityPub::ProcessAccountService.new.call(@username, @domain, actor_json)
  80. rescue Oj::ParseError
  81. nil
  82. end
  83. def create_account
  84. Rails.logger.debug "Creating new remote account for #{@username}@#{@domain}"
  85. @account = Account.new(username: @username, domain: @domain)
  86. @account.suspended = true if auto_suspend?
  87. @account.silenced = true if auto_silence?
  88. @account.private_key = nil
  89. end
  90. def update_account
  91. @account.last_webfingered_at = Time.now.utc
  92. @account.protocol = :ostatus
  93. @account.remote_url = atom_url
  94. @account.salmon_url = salmon_url
  95. @account.url = url
  96. @account.public_key = public_key
  97. @account.uri = canonical_uri
  98. @account.hub_url = hub_url
  99. @account.save!
  100. end
  101. def auto_suspend?
  102. domain_block&.suspend?
  103. end
  104. def auto_silence?
  105. domain_block&.silence?
  106. end
  107. def domain_block
  108. return @domain_block if defined?(@domain_block)
  109. @domain_block = DomainBlock.find_by(domain: @domain)
  110. end
  111. def atom_url
  112. @atom_url ||= @webfinger.link('http://schemas.google.com/g/2010#updates-from').href
  113. end
  114. def salmon_url
  115. @salmon_url ||= @webfinger.link('salmon').href
  116. end
  117. def actor_url
  118. @actor_url ||= @webfinger.link('self').href
  119. end
  120. def url
  121. @url ||= @webfinger.link('http://webfinger.net/rel/profile-page').href
  122. end
  123. def public_key
  124. @public_key ||= magic_key_to_pem(@webfinger.link('magic-public-key').href)
  125. end
  126. def canonical_uri
  127. return @canonical_uri if defined?(@canonical_uri)
  128. author_uri = atom.at_xpath('/xmlns:feed/xmlns:author/xmlns:uri')
  129. if author_uri.nil?
  130. owner = atom.at_xpath('/xmlns:feed').at_xpath('./dfrn:owner', dfrn: DFRN_NS)
  131. author_uri = owner.at_xpath('./xmlns:uri') unless owner.nil?
  132. end
  133. @canonical_uri = author_uri.nil? ? nil : author_uri.content
  134. end
  135. def hub_url
  136. return @hub_url if defined?(@hub_url)
  137. hubs = atom.xpath('//xmlns:link[@rel="hub"]')
  138. @hub_url = hubs.empty? || hubs.first['href'].nil? ? nil : hubs.first['href']
  139. end
  140. def atom_body
  141. return @atom_body if defined?(@atom_body)
  142. @atom_body = Request.new(:get, atom_url).perform do |response|
  143. raise Mastodon::UnexpectedResponseError, response unless response.code == 200
  144. response.body_with_limit
  145. end
  146. end
  147. def actor_json
  148. return @actor_json if defined?(@actor_json)
  149. json = fetch_resource(actor_url, false)
  150. @actor_json = supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) ? json : nil
  151. end
  152. def atom
  153. return @atom if defined?(@atom)
  154. @atom = Nokogiri::XML(atom_body)
  155. end
  156. def update_account_profile
  157. RemoteProfileUpdateWorker.perform_async(@account.id, atom_body.force_encoding('UTF-8'), false)
  158. end
  159. def lock_options
  160. { redis: Redis.current, key: "resolve:#{@username}@#{@domain}" }
  161. end
  162. end