The code powering m.abunchtell.com https://m.abunchtell.com
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.
 
 
 
 

221 lines
6.1 KiB

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