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.

151 lines
4.4 KiB

8 years ago
8 years ago
  1. # frozen_string_literal: true
  2. class ResolveAccountService < BaseService
  3. include JsonLdHelper
  4. include DomainControlHelper
  5. include WebfingerHelper
  6. # Find or create an account record for a remote user. When creating,
  7. # look up the user's webfinger and fetch ActivityPub data
  8. # @param [String, Account] uri URI in the username@domain format or account record
  9. # @param [Hash] options
  10. # @option options [Boolean] :redirected Do not follow further Webfinger redirects
  11. # @option options [Boolean] :skip_webfinger Do not attempt any webfinger query or refreshing account data
  12. # @return [Account]
  13. def call(uri, options = {})
  14. return if uri.blank?
  15. process_options!(uri, options)
  16. # First of all we want to check if we've got the account
  17. # record with the URI already, and if so, we can exit early
  18. return if domain_not_allowed?(@domain)
  19. @account ||= Account.find_remote(@username, @domain)
  20. return @account if @account&.local? || @domain.nil? || !webfinger_update_due?
  21. # At this point we are in need of a Webfinger query, which may
  22. # yield us a different username/domain through a redirect
  23. process_webfinger!(@uri)
  24. @domain = nil if TagManager.instance.local_domain?(@domain)
  25. # Because the username/domain pair may be different than what
  26. # we already checked, we need to check if we've already got
  27. # the record with that URI, again
  28. return if domain_not_allowed?(@domain)
  29. @account ||= Account.find_remote(@username, @domain)
  30. if gone_from_origin? && not_yet_deleted?
  31. queue_deletion!
  32. return
  33. end
  34. return @account if @account&.local? || gone_from_origin? || !webfinger_update_due?
  35. # Now it is certain, it is definitely a remote account, and it
  36. # either needs to be created, or updated from fresh data
  37. fetch_account!
  38. rescue Webfinger::Error, Oj::ParseError => e
  39. Rails.logger.debug "Webfinger query for #{@uri} failed: #{e}"
  40. nil
  41. end
  42. private
  43. def process_options!(uri, options)
  44. @options = options
  45. if uri.is_a?(Account)
  46. @account = uri
  47. @username = @account.username
  48. @domain = @account.domain
  49. else
  50. @username, @domain = uri.split('@')
  51. end
  52. @domain = begin
  53. if TagManager.instance.local_domain?(@domain)
  54. nil
  55. else
  56. TagManager.instance.normalize_domain(@domain)
  57. end
  58. end
  59. @uri = [@username, @domain].compact.join('@')
  60. end
  61. def process_webfinger!(uri)
  62. @webfinger = webfinger!("acct:#{uri}")
  63. confirmed_username, confirmed_domain = split_acct(@webfinger.subject)
  64. if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
  65. @username = confirmed_username
  66. @domain = confirmed_domain
  67. return
  68. end
  69. # Account doesn't match, so it may have been redirected
  70. @webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}")
  71. @username, @domain = split_acct(@webfinger.subject)
  72. unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
  73. raise Webfinger::RedirectError, "The URI #{uri} tries to hijack #{@username}@#{@domain}"
  74. end
  75. rescue Webfinger::GoneError
  76. @gone = true
  77. end
  78. def split_acct(acct)
  79. acct.gsub(/\Aacct:/, '').split('@')
  80. end
  81. def fetch_account!
  82. return unless activitypub_ready?
  83. RedisLock.acquire(lock_options) do |lock|
  84. if lock.acquired?
  85. @account = ActivityPub::FetchRemoteAccountService.new.call(actor_url)
  86. else
  87. raise Mastodon::RaceConditionError
  88. end
  89. end
  90. @account
  91. end
  92. def webfinger_update_due?
  93. return false if @options[:check_delivery_availability] && !DeliveryFailureTracker.available?(@domain)
  94. return false if @options[:skip_webfinger]
  95. @account.nil? || @account.possibly_stale?
  96. end
  97. def activitypub_ready?
  98. ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self', 'type'))
  99. end
  100. def actor_url
  101. @actor_url ||= @webfinger.link('self', 'href')
  102. end
  103. def gone_from_origin?
  104. @gone
  105. end
  106. def not_yet_deleted?
  107. @account.present? && !@account.local?
  108. end
  109. def queue_deletion!
  110. AccountDeletionWorker.perform_async(@account.id, reserve_username: false, skip_activitypub: true)
  111. end
  112. def lock_options
  113. { redis: Redis.current, key: "resolve:#{@username}@#{@domain}", autorelease: 15.minutes.seconds }
  114. end
  115. end