resolve_account_service_spec.rb 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. # frozen_string_literal: true
  2. require 'rails_helper'
  3. RSpec.describe ResolveAccountService do
  4. subject { described_class.new }
  5. before do
  6. stub_request(:get, 'https://example.com/.well-known/host-meta').to_return(status: 404)
  7. stub_request(:get, 'https://quitter.no/avatar/7477-300-20160211190340.png').to_return(request_fixture('avatar.txt'))
  8. stub_request(:get, 'https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com').to_return(request_fixture('activitypub-webfinger.txt'))
  9. stub_request(:get, 'https://ap.example.com/users/foo').to_return(request_fixture('activitypub-actor.txt'))
  10. stub_request(:get, 'https://ap.example.com/users/foo.atom').to_return(request_fixture('activitypub-feed.txt'))
  11. stub_request(:get, %r{https://ap\.example\.com/users/foo/\w+}).to_return(status: 404)
  12. stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:hoge@example.com').to_return(status: 410)
  13. end
  14. context 'when using skip_webfinger' do
  15. context 'when account is known' do
  16. let!(:remote_account) { Fabricate(:account, username: 'foo', domain: 'ap.example.com', protocol: 'activitypub') }
  17. context 'when domain is banned' do
  18. before { Fabricate(:domain_block, domain: 'ap.example.com', severity: :suspend) }
  19. it 'does not return an account or make a webfinger query' do
  20. expect(subject.call('foo@ap.example.com', skip_webfinger: true))
  21. .to be_nil
  22. expect(webfinger_discovery_request)
  23. .to_not have_been_made
  24. end
  25. end
  26. context 'when domain is not banned' do
  27. it 'returns the expected account and does not make a webfinger query' do
  28. expect(subject.call('foo@ap.example.com', skip_webfinger: true))
  29. .to eq remote_account
  30. expect(webfinger_discovery_request)
  31. .to_not have_been_made
  32. end
  33. end
  34. end
  35. context 'when account is not known' do
  36. it 'does not return an account and does not make webfinger query' do
  37. expect(subject.call('foo@ap.example.com', skip_webfinger: true))
  38. .to be_nil
  39. expect(webfinger_discovery_request)
  40. .to_not have_been_made
  41. end
  42. end
  43. def webfinger_discovery_request
  44. a_request(
  45. :get,
  46. 'https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com'
  47. )
  48. end
  49. end
  50. context 'when there is an LRDD endpoint but no resolvable account' do
  51. before do
  52. stub_request(:get, 'https://quitter.no/.well-known/host-meta').to_return(request_fixture('.host-meta.txt'))
  53. stub_request(:get, 'https://quitter.no/.well-known/webfinger?resource=acct:catsrgr8@quitter.no').to_return(status: 404)
  54. end
  55. it 'returns nil' do
  56. expect(subject.call('catsrgr8@quitter.no')).to be_nil
  57. end
  58. end
  59. context 'when there is no LRDD endpoint nor resolvable account' do
  60. before do
  61. stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:catsrgr8@example.com').to_return(status: 404)
  62. end
  63. it 'returns nil' do
  64. expect(subject.call('catsrgr8@example.com')).to be_nil
  65. end
  66. end
  67. context 'when webfinger returns http gone' do
  68. context 'with a previously known account' do
  69. before do
  70. Fabricate(:account, username: 'hoge', domain: 'example.com', last_webfingered_at: nil)
  71. allow(AccountDeletionWorker).to receive(:perform_async)
  72. end
  73. it 'returns nil and queues deletion worker' do
  74. expect(subject.call('hoge@example.com'))
  75. .to be_nil
  76. expect(AccountDeletionWorker)
  77. .to have_received(:perform_async)
  78. end
  79. end
  80. context 'with a previously unknown account' do
  81. it 'returns nil' do
  82. expect(subject.call('hoge@example.com')).to be_nil
  83. end
  84. end
  85. end
  86. context 'with a legitimate webfinger redirection' do
  87. before do
  88. webfinger = { subject: 'acct:foo@ap.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] }
  89. stub_request(:get, 'https://redirected.example.com/.well-known/webfinger?resource=acct:Foo@redirected.example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
  90. end
  91. it 'returns new remote account' do
  92. account = subject.call('Foo@redirected.example.com')
  93. expect(account)
  94. .to have_attributes(
  95. activitypub?: true,
  96. acct: 'foo@ap.example.com',
  97. inbox_url: 'https://ap.example.com/users/foo/inbox'
  98. )
  99. end
  100. end
  101. context 'with a misconfigured redirection' do
  102. before do
  103. webfinger = { subject: 'acct:Foo@redirected.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] }
  104. stub_request(:get, 'https://redirected.example.com/.well-known/webfinger?resource=acct:Foo@redirected.example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
  105. end
  106. it 'returns new remote account' do
  107. account = subject.call('Foo@redirected.example.com')
  108. expect(account)
  109. .to have_attributes(
  110. activitypub?: true,
  111. acct: 'foo@ap.example.com',
  112. inbox_url: 'https://ap.example.com/users/foo/inbox'
  113. )
  114. end
  115. end
  116. context 'with too many webfinger redirections' do
  117. before do
  118. webfinger = { subject: 'acct:foo@evil.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] }
  119. stub_request(:get, 'https://redirected.example.com/.well-known/webfinger?resource=acct:Foo@redirected.example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
  120. webfinger2 = { subject: 'acct:foo@ap.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] }
  121. stub_request(:get, 'https://evil.example.com/.well-known/webfinger?resource=acct:foo@evil.example.com').to_return(body: Oj.dump(webfinger2), headers: { 'Content-Type': 'application/jrd+json' })
  122. end
  123. it 'does not return a new remote account' do
  124. expect(subject.call('Foo@redirected.example.com')).to be_nil
  125. end
  126. end
  127. context 'with webfinger response subject missing a host value' do
  128. let(:body) { Oj.dump({ subject: 'user@' }) }
  129. let(:url) { 'https://host.example/.well-known/webfinger?resource=acct:user@host.example' }
  130. before do
  131. stub_request(:get, url).to_return(status: 200, body: body)
  132. end
  133. it 'returns nil with incomplete subject in response' do
  134. expect(subject.call('user@host.example')).to be_nil
  135. end
  136. end
  137. context 'with an ActivityPub account' do
  138. it 'returns new remote account' do
  139. account = subject.call('foo@ap.example.com')
  140. expect(account)
  141. .to have_attributes(
  142. activitypub?: true,
  143. domain: 'ap.example.com',
  144. inbox_url: 'https://ap.example.com/users/foo/inbox'
  145. )
  146. end
  147. context 'with multiple types' do
  148. before do
  149. stub_request(:get, 'https://ap.example.com/users/foo').to_return(request_fixture('activitypub-actor-individual.txt'))
  150. end
  151. it 'returns new remote account' do
  152. account = subject.call('foo@ap.example.com')
  153. expect(account)
  154. .to have_attributes(
  155. activitypub?: true,
  156. domain: 'ap.example.com',
  157. inbox_url: 'https://ap.example.com/users/foo/inbox',
  158. actor_type: 'Person'
  159. )
  160. end
  161. end
  162. end
  163. context 'with an already-known actor changing acct: URI' do
  164. let!(:duplicate) { Fabricate(:account, username: 'foo', domain: 'old.example.com', uri: 'https://ap.example.com/users/foo') }
  165. let!(:status) { Fabricate(:status, account: duplicate, text: 'foo') }
  166. it 'returns new remote account and merges accounts', :inline_jobs do
  167. account = subject.call('foo@ap.example.com')
  168. expect(account)
  169. .to have_attributes(
  170. activitypub?: true,
  171. domain: 'ap.example.com',
  172. inbox_url: 'https://ap.example.com/users/foo/inbox',
  173. uri: 'https://ap.example.com/users/foo'
  174. )
  175. expect(status.reload.account_id)
  176. .to eq account.id
  177. expect(Account.where(uri: account.uri).count)
  178. .to eq 1
  179. end
  180. end
  181. context 'with an already-known acct: URI changing ActivityPub id' do
  182. let!(:old_account) { Fabricate(:account, username: 'foo', domain: 'ap.example.com', uri: 'https://old.example.com/users/foo', last_webfingered_at: nil) }
  183. let!(:status) { Fabricate(:status, account: old_account, text: 'foo') }
  184. it 'returns new remote account' do
  185. account = subject.call('foo@ap.example.com')
  186. expect(account)
  187. .to have_attributes(
  188. activitypub?: true,
  189. domain: 'ap.example.com',
  190. inbox_url: 'https://ap.example.com/users/foo/inbox',
  191. uri: 'https://ap.example.com/users/foo'
  192. )
  193. expect(status.reload.account)
  194. .to eq(account)
  195. end
  196. end
  197. it 'processes one remote account at a time using locks' do
  198. fail_occurred = false
  199. return_values = Concurrent::Array.new
  200. multi_threaded_execution(5) do
  201. begin
  202. return_values << described_class.new.call('foo@ap.example.com')
  203. rescue ActiveRecord::RecordNotUnique
  204. fail_occurred = true
  205. ensure
  206. RedisConnection.pool.checkin if Thread.current[:redis]
  207. end
  208. end
  209. expect(fail_occurred).to be false
  210. expect(return_values).to_not include(nil)
  211. end
  212. end