process_collection_service_spec.rb 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. # frozen_string_literal: true
  2. require 'rails_helper'
  3. RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
  4. subject { described_class.new }
  5. let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
  6. let(:payload) do
  7. {
  8. '@context': 'https://www.w3.org/ns/activitystreams',
  9. id: 'foo',
  10. type: 'Create',
  11. actor: ActivityPub::TagManager.instance.uri_for(actor),
  12. object: {
  13. id: 'bar',
  14. type: 'Note',
  15. content: 'Lorem ipsum',
  16. },
  17. }
  18. end
  19. let(:json) { Oj.dump(payload) }
  20. describe '#call' do
  21. context 'when actor is suspended' do
  22. before do
  23. actor.suspend!(origin: :remote)
  24. end
  25. %w(Accept Add Announce Block Create Flag Follow Like Move Remove).each do |activity_type|
  26. context "with #{activity_type} activity" do
  27. let(:payload) do
  28. {
  29. '@context': 'https://www.w3.org/ns/activitystreams',
  30. id: 'foo',
  31. type: activity_type,
  32. actor: ActivityPub::TagManager.instance.uri_for(actor),
  33. }
  34. end
  35. it 'does not process payload' do
  36. allow(ActivityPub::Activity).to receive(:factory)
  37. subject.call(json, actor)
  38. expect(ActivityPub::Activity).to_not have_received(:factory)
  39. end
  40. end
  41. end
  42. %w(Delete Reject Undo Update).each do |activity_type|
  43. context "with #{activity_type} activity" do
  44. let(:payload) do
  45. {
  46. '@context': 'https://www.w3.org/ns/activitystreams',
  47. id: 'foo',
  48. type: activity_type,
  49. actor: ActivityPub::TagManager.instance.uri_for(actor),
  50. }
  51. end
  52. it 'processes the payload' do
  53. allow(ActivityPub::Activity).to receive(:factory)
  54. subject.call(json, actor)
  55. expect(ActivityPub::Activity).to have_received(:factory)
  56. end
  57. end
  58. end
  59. end
  60. context 'when actor differs from sender' do
  61. let(:forwarder) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/other_account') }
  62. it 'does not process payload if no signature exists' do
  63. signature_double = instance_double(ActivityPub::LinkedDataSignature, verify_actor!: nil)
  64. allow(ActivityPub::LinkedDataSignature).to receive(:new).and_return(signature_double)
  65. allow(ActivityPub::Activity).to receive(:factory)
  66. subject.call(json, forwarder)
  67. expect(ActivityPub::Activity).to_not have_received(:factory)
  68. end
  69. it 'processes payload with actor if valid signature exists' do
  70. payload['signature'] = { 'type' => 'RsaSignature2017' }
  71. signature_double = instance_double(ActivityPub::LinkedDataSignature, verify_actor!: actor)
  72. allow(ActivityPub::LinkedDataSignature).to receive(:new).and_return(signature_double)
  73. allow(ActivityPub::Activity).to receive(:factory).with(instance_of(Hash), actor, instance_of(Hash))
  74. subject.call(json, forwarder)
  75. expect(ActivityPub::Activity).to have_received(:factory).with(instance_of(Hash), actor, instance_of(Hash))
  76. end
  77. it 'does not process payload if invalid signature exists' do
  78. payload['signature'] = { 'type' => 'RsaSignature2017' }
  79. signature_double = instance_double(ActivityPub::LinkedDataSignature, verify_actor!: nil)
  80. allow(ActivityPub::LinkedDataSignature).to receive(:new).and_return(signature_double)
  81. allow(ActivityPub::Activity).to receive(:factory)
  82. subject.call(json, forwarder)
  83. expect(ActivityPub::Activity).to_not have_received(:factory)
  84. end
  85. context 'when receiving a fabricated status' do
  86. let!(:actor) do
  87. Fabricate(:account,
  88. username: 'bob',
  89. domain: 'example.com',
  90. uri: 'https://example.com/users/bob',
  91. private_key: nil,
  92. public_key: <<~TEXT)
  93. -----BEGIN PUBLIC KEY-----
  94. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuuYyoyfsRkYnXRotMsId
  95. W3euBDDfiv9oVqOxUVC7bhel8KednIMrMCRWFAkgJhbrlzbIkjVr68o1MP9qLcn7
  96. CmH/BXHp7yhuFTr4byjdJKpwB+/i2jNEsvDH5jR8WTAeTCe0x/QHg21V3F7dSI5m
  97. CCZ/1dSIyOXLRTWVlfDlm3rE4ntlCo+US3/7oSWbg/4/4qEnt1HC32kvklgScxua
  98. 4LR5ATdoXa5bFoopPWhul7MJ6NyWCyQyScUuGdlj8EN4kmKQJvphKHrI9fvhgOuG
  99. TvhTR1S5InA4azSSchY0tXEEw/VNxraeX0KPjbgr6DPcwhPd/m0nhVDq0zVyVBBD
  100. MwIDAQAB
  101. -----END PUBLIC KEY-----
  102. TEXT
  103. end
  104. let(:payload) do
  105. {
  106. '@context': [
  107. 'https://www.w3.org/ns/activitystreams',
  108. nil,
  109. { object: 'https://www.w3.org/ns/activitystreams#object' },
  110. ],
  111. id: 'https://example.com/users/bob/fake-status/activity',
  112. type: 'Create',
  113. actor: 'https://example.com/users/bob',
  114. published: '2022-01-22T15:00:00Z',
  115. to: [
  116. 'https://www.w3.org/ns/activitystreams#Public',
  117. ],
  118. cc: [
  119. 'https://example.com/users/bob/followers',
  120. ],
  121. signature: {
  122. type: 'RsaSignature2017',
  123. creator: 'https://example.com/users/bob#main-key',
  124. created: '2022-03-09T21:57:25Z',
  125. signatureValue: 'WculK0LelTQ0MvGwU9TPoq5pFzFfGYRDCJqjZ232/Udj4' \
  126. 'CHqDTGOSw5UTDLShqBOyycCkbZGrQwXG+dpyDpQLSe1UV' \
  127. 'PZ5TPQtc/9XtI57WlS2nMNpdvRuxGnnb2btPdesXZ7n3p' \
  128. 'Cxo0zjaXrJMe0mqQh5QJO22mahb4bDwwmfTHgbD3nmkD+' \
  129. 'fBfGi+UV2qWwqr+jlV4L4JqNkh0gWljF5KTePLRRZCuWi' \
  130. 'Q/FAt7c67636cdIPf7fR+usjuZltTQyLZKEGuK8VUn2Gk' \
  131. 'fsx5qns7Vcjvlz1JqlAjyO8HPBbzTTHzUG2nUOIgC3Poj' \
  132. 'CSWv6mNTmRGoLZzOscCAYQA6cKw==',
  133. },
  134. '@id': 'https://example.com/users/bob/statuses/107928807471117876/activity',
  135. '@type': 'https://www.w3.org/ns/activitystreams#Create',
  136. 'https://www.w3.org/ns/activitystreams#actor': {
  137. '@id': 'https://example.com/users/bob',
  138. },
  139. 'https://www.w3.org/ns/activitystreams#cc': {
  140. '@id': 'https://example.com/users/bob/followers',
  141. },
  142. object: {
  143. id: 'https://example.com/users/bob/fake-status',
  144. type: 'Note',
  145. published: '2022-01-22T15:00:00Z',
  146. url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=puck-was-here',
  147. attributedTo: 'https://example.com/users/bob',
  148. to: [
  149. 'https://www.w3.org/ns/activitystreams#Public',
  150. ],
  151. cc: [
  152. 'https://example.com/users/bob/followers',
  153. ],
  154. sensitive: false,
  155. atomUri: 'https://example.com/users/bob/fake-status',
  156. conversation: 'tag:example.com,2022-03-09:objectId=15:objectType=Conversation',
  157. content: '<p>puck was here</p>',
  158. '@id': 'https://example.com/users/bob/statuses/107928807471117876',
  159. '@type': 'https://www.w3.org/ns/activitystreams#Note',
  160. 'http://ostatus.org#atomUri': 'https://example.com/users/bob/statuses/107928807471117876',
  161. 'http://ostatus.org#conversation': 'tag:example.com,2022-03-09:objectId=15:objectType=Conversation',
  162. 'https://www.w3.org/ns/activitystreams#attachment': [],
  163. 'https://www.w3.org/ns/activitystreams#attributedTo': {
  164. '@id': 'https://example.com/users/bob',
  165. },
  166. 'https://www.w3.org/ns/activitystreams#cc': {
  167. '@id': 'https://example.com/users/bob/followers',
  168. },
  169. 'https://www.w3.org/ns/activitystreams#content': [
  170. '<p>hello world</p>',
  171. {
  172. '@value': '<p>hello world</p>',
  173. '@language': 'en',
  174. },
  175. ],
  176. 'https://www.w3.org/ns/activitystreams#published': {
  177. '@type': 'http://www.w3.org/2001/XMLSchema#dateTime',
  178. '@value': '2022-03-09T21:55:07Z',
  179. },
  180. 'https://www.w3.org/ns/activitystreams#replies': {
  181. '@id': 'https://example.com/users/bob/statuses/107928807471117876/replies',
  182. '@type': 'https://www.w3.org/ns/activitystreams#Collection',
  183. 'https://www.w3.org/ns/activitystreams#first': {
  184. '@type': 'https://www.w3.org/ns/activitystreams#CollectionPage',
  185. 'https://www.w3.org/ns/activitystreams#items': [],
  186. 'https://www.w3.org/ns/activitystreams#next': {
  187. '@id': 'https://example.com/users/bob/statuses/107928807471117876/replies?only_other_accounts=true&page=true',
  188. },
  189. 'https://www.w3.org/ns/activitystreams#partOf': {
  190. '@id': 'https://example.com/users/bob/statuses/107928807471117876/replies',
  191. },
  192. },
  193. },
  194. 'https://www.w3.org/ns/activitystreams#sensitive': false,
  195. 'https://www.w3.org/ns/activitystreams#tag': [],
  196. 'https://www.w3.org/ns/activitystreams#to': {
  197. '@id': 'https://www.w3.org/ns/activitystreams#Public',
  198. },
  199. 'https://www.w3.org/ns/activitystreams#url': {
  200. '@id': 'https://example.com/@bob/107928807471117876',
  201. },
  202. },
  203. 'https://www.w3.org/ns/activitystreams#published': {
  204. '@type': 'http://www.w3.org/2001/XMLSchema#dateTime',
  205. '@value': '2022-03-09T21:55:07Z',
  206. },
  207. 'https://www.w3.org/ns/activitystreams#to': {
  208. '@id': 'https://www.w3.org/ns/activitystreams#Public',
  209. },
  210. }
  211. end
  212. it 'does not process forged payload' do
  213. allow(ActivityPub::Activity).to receive(:factory)
  214. expect { subject.call(json, forwarder) }
  215. .to_not change(actor.reload.statuses, :count)
  216. expect(ActivityPub::Activity).to_not have_received(:factory).with(
  217. hash_including(
  218. 'object' => hash_including(
  219. 'id' => 'https://example.com/users/bob/fake-status'
  220. )
  221. ),
  222. anything,
  223. anything
  224. )
  225. expect(ActivityPub::Activity).to_not have_received(:factory).with(
  226. hash_including(
  227. 'object' => hash_including(
  228. 'content' => '<p>puck was here</p>'
  229. )
  230. ),
  231. anything,
  232. anything
  233. )
  234. expect(Status.exists?(uri: 'https://example.com/users/bob/fake-status')).to be false
  235. end
  236. end
  237. end
  238. end
  239. end