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