Merge develop
[akkoma] / test / web / activity_pub / activity_pub_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
6 use Pleroma.Web.ConnCase
7 import Pleroma.Factory
8 alias Pleroma.Activity
9 alias Pleroma.Instances
10 alias Pleroma.Object
11 alias Pleroma.User
12 alias Pleroma.Web.ActivityPub.ObjectView
13 alias Pleroma.Web.ActivityPub.UserView
14 alias Pleroma.Web.ActivityPub.Utils
15
16 setup_all do
17 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
18
19 config_path = [:instance, :federating]
20 initial_setting = Pleroma.Config.get(config_path)
21
22 Pleroma.Config.put(config_path, true)
23 on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
24
25 :ok
26 end
27
28 describe "/relay" do
29 test "with the relay active, it returns the relay user", %{conn: conn} do
30 res =
31 conn
32 |> get(activity_pub_path(conn, :relay))
33 |> json_response(200)
34
35 assert res["id"] =~ "/relay"
36 end
37
38 test "with the relay disabled, it returns 404", %{conn: conn} do
39 Pleroma.Config.put([:instance, :allow_relay], false)
40
41 conn
42 |> get(activity_pub_path(conn, :relay))
43 |> json_response(404)
44 |> assert
45
46 Pleroma.Config.put([:instance, :allow_relay], true)
47 end
48 end
49
50 describe "/users/:nickname" do
51 test "it returns a json representation of the user with accept application/json", %{
52 conn: conn
53 } do
54 user = insert(:user)
55
56 conn =
57 conn
58 |> put_req_header("accept", "application/json")
59 |> get("/users/#{user.nickname}")
60
61 user = User.get_cached_by_id(user.id)
62
63 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
64 end
65
66 test "it returns a json representation of the user with accept application/activity+json", %{
67 conn: conn
68 } do
69 user = insert(:user)
70
71 conn =
72 conn
73 |> put_req_header("accept", "application/activity+json")
74 |> get("/users/#{user.nickname}")
75
76 user = User.get_cached_by_id(user.id)
77
78 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
79 end
80
81 test "it returns a json representation of the user with accept application/ld+json", %{
82 conn: conn
83 } do
84 user = insert(:user)
85
86 conn =
87 conn
88 |> put_req_header(
89 "accept",
90 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
91 )
92 |> get("/users/#{user.nickname}")
93
94 user = User.get_cached_by_id(user.id)
95
96 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
97 end
98 end
99
100 describe "/object/:uuid" do
101 test "it returns a json representation of the object with accept application/json", %{
102 conn: conn
103 } do
104 note = insert(:note)
105 uuid = String.split(note.data["id"], "/") |> List.last()
106
107 conn =
108 conn
109 |> put_req_header("accept", "application/json")
110 |> get("/objects/#{uuid}")
111
112 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
113 end
114
115 test "it returns a json representation of the object with accept application/activity+json",
116 %{conn: conn} do
117 note = insert(:note)
118 uuid = String.split(note.data["id"], "/") |> List.last()
119
120 conn =
121 conn
122 |> put_req_header("accept", "application/activity+json")
123 |> get("/objects/#{uuid}")
124
125 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
126 end
127
128 test "it returns a json representation of the object with accept application/ld+json", %{
129 conn: conn
130 } do
131 note = insert(:note)
132 uuid = String.split(note.data["id"], "/") |> List.last()
133
134 conn =
135 conn
136 |> put_req_header(
137 "accept",
138 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
139 )
140 |> get("/objects/#{uuid}")
141
142 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
143 end
144
145 test "it returns 404 for non-public messages", %{conn: conn} do
146 note = insert(:direct_note)
147 uuid = String.split(note.data["id"], "/") |> List.last()
148
149 conn =
150 conn
151 |> put_req_header("accept", "application/activity+json")
152 |> get("/objects/#{uuid}")
153
154 assert json_response(conn, 404)
155 end
156
157 test "it returns 404 for tombstone objects", %{conn: conn} do
158 tombstone = insert(:tombstone)
159 uuid = String.split(tombstone.data["id"], "/") |> List.last()
160
161 conn =
162 conn
163 |> put_req_header("accept", "application/activity+json")
164 |> get("/objects/#{uuid}")
165
166 assert json_response(conn, 404)
167 end
168 end
169
170 describe "/object/:uuid/likes" do
171 test "it returns the like activities in a collection", %{conn: conn} do
172 like = insert(:like_activity)
173 uuid = String.split(like.data["object"], "/") |> List.last()
174
175 result =
176 conn
177 |> put_req_header("accept", "application/activity+json")
178 |> get("/objects/#{uuid}/likes")
179 |> json_response(200)
180
181 assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"]
182 end
183 end
184
185 describe "/activities/:uuid" do
186 test "it returns a json representation of the activity", %{conn: conn} do
187 activity = insert(:note_activity)
188 uuid = String.split(activity.data["id"], "/") |> List.last()
189
190 conn =
191 conn
192 |> put_req_header("accept", "application/activity+json")
193 |> get("/activities/#{uuid}")
194
195 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
196 end
197
198 test "it returns 404 for non-public activities", %{conn: conn} do
199 activity = insert(:direct_note_activity)
200 uuid = String.split(activity.data["id"], "/") |> List.last()
201
202 conn =
203 conn
204 |> put_req_header("accept", "application/activity+json")
205 |> get("/activities/#{uuid}")
206
207 assert json_response(conn, 404)
208 end
209 end
210
211 describe "/inbox" do
212 test "it inserts an incoming activity into the database", %{conn: conn} do
213 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
214
215 conn =
216 conn
217 |> assign(:valid_signature, true)
218 |> put_req_header("content-type", "application/activity+json")
219 |> post("/inbox", data)
220
221 assert "ok" == json_response(conn, 200)
222 :timer.sleep(500)
223 assert Activity.get_by_ap_id(data["id"])
224 end
225
226 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
227 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
228
229 sender_url = data["actor"]
230 Instances.set_consistently_unreachable(sender_url)
231 refute Instances.reachable?(sender_url)
232
233 conn =
234 conn
235 |> assign(:valid_signature, true)
236 |> put_req_header("content-type", "application/activity+json")
237 |> post("/inbox", data)
238
239 assert "ok" == json_response(conn, 200)
240 assert Instances.reachable?(sender_url)
241 end
242 end
243
244 describe "/users/:nickname/inbox" do
245 setup do
246 data =
247 File.read!("test/fixtures/mastodon-post-activity.json")
248 |> Poison.decode!()
249
250 [data: data]
251 end
252
253 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
254 user = insert(:user)
255 data = Map.put(data, "bcc", [user.ap_id])
256
257 conn =
258 conn
259 |> assign(:valid_signature, true)
260 |> put_req_header("content-type", "application/activity+json")
261 |> post("/users/#{user.nickname}/inbox", data)
262
263 assert "ok" == json_response(conn, 200)
264 :timer.sleep(500)
265 assert Activity.get_by_ap_id(data["id"])
266 end
267
268 test "it accepts messages from actors that are followed by the user", %{
269 conn: conn,
270 data: data
271 } do
272 recipient = insert(:user)
273 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
274
275 {:ok, recipient} = User.follow(recipient, actor)
276
277 object =
278 data["object"]
279 |> Map.put("attributedTo", actor.ap_id)
280
281 data =
282 data
283 |> Map.put("actor", actor.ap_id)
284 |> Map.put("object", object)
285
286 conn =
287 conn
288 |> assign(:valid_signature, true)
289 |> put_req_header("content-type", "application/activity+json")
290 |> post("/users/#{recipient.nickname}/inbox", data)
291
292 assert "ok" == json_response(conn, 200)
293 :timer.sleep(500)
294 assert Activity.get_by_ap_id(data["id"])
295 end
296
297 test "it rejects reads from other users", %{conn: conn} do
298 user = insert(:user)
299 otheruser = insert(:user)
300
301 conn =
302 conn
303 |> assign(:user, otheruser)
304 |> put_req_header("accept", "application/activity+json")
305 |> get("/users/#{user.nickname}/inbox")
306
307 assert json_response(conn, 403)
308 end
309
310 test "it returns a note activity in a collection", %{conn: conn} do
311 note_activity = insert(:direct_note_activity)
312 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
313
314 conn =
315 conn
316 |> assign(:user, user)
317 |> put_req_header("accept", "application/activity+json")
318 |> get("/users/#{user.nickname}/inbox")
319
320 assert response(conn, 200) =~ note_activity.data["object"]["content"]
321 end
322
323 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
324 user = insert(:user)
325 data = Map.put(data, "bcc", [user.ap_id])
326
327 sender_host = URI.parse(data["actor"]).host
328 Instances.set_consistently_unreachable(sender_host)
329 refute Instances.reachable?(sender_host)
330
331 conn =
332 conn
333 |> assign(:valid_signature, true)
334 |> put_req_header("content-type", "application/activity+json")
335 |> post("/users/#{user.nickname}/inbox", data)
336
337 assert "ok" == json_response(conn, 200)
338 assert Instances.reachable?(sender_host)
339 end
340
341 test "it removes all follower collections but actor's", %{conn: conn} do
342 [actor, recipient] = insert_pair(:user)
343
344 data =
345 File.read!("test/fixtures/activitypub-client-post-activity.json")
346 |> Poison.decode!()
347
348 object = Map.put(data["object"], "attributedTo", actor.ap_id)
349
350 data =
351 data
352 |> Map.put("id", Utils.generate_object_id())
353 |> Map.put("actor", actor.ap_id)
354 |> Map.put("object", object)
355 |> Map.put("cc", [
356 recipient.follower_address,
357 actor.follower_address
358 ])
359 |> Map.put("to", [
360 recipient.ap_id,
361 recipient.follower_address,
362 "https://www.w3.org/ns/activitystreams#Public"
363 ])
364
365 conn
366 |> assign(:valid_signature, true)
367 |> put_req_header("content-type", "application/activity+json")
368 |> post("/users/#{recipient.nickname}/inbox", data)
369 |> json_response(200)
370
371 activity = Activity.get_by_ap_id(data["id"])
372
373 assert activity.id
374 assert actor.follower_address in activity.recipients
375 assert actor.follower_address in activity.data["cc"]
376
377 refute recipient.follower_address in activity.recipients
378 refute recipient.follower_address in activity.data["cc"]
379 refute recipient.follower_address in activity.data["to"]
380 end
381 end
382
383 describe "/users/:nickname/outbox" do
384 test "it will not bomb when there is no activity", %{conn: conn} do
385 user = insert(:user)
386
387 conn =
388 conn
389 |> put_req_header("accept", "application/activity+json")
390 |> get("/users/#{user.nickname}/outbox")
391
392 result = json_response(conn, 200)
393 assert user.ap_id <> "/outbox" == result["id"]
394 end
395
396 test "it returns a note activity in a collection", %{conn: conn} do
397 note_activity = insert(:note_activity)
398 user = User.get_cached_by_ap_id(note_activity.data["actor"])
399
400 conn =
401 conn
402 |> put_req_header("accept", "application/activity+json")
403 |> get("/users/#{user.nickname}/outbox")
404
405 assert response(conn, 200) =~ note_activity.data["object"]["content"]
406 end
407
408 test "it returns an announce activity in a collection", %{conn: conn} do
409 announce_activity = insert(:announce_activity)
410 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
411
412 conn =
413 conn
414 |> put_req_header("accept", "application/activity+json")
415 |> get("/users/#{user.nickname}/outbox")
416
417 assert response(conn, 200) =~ announce_activity.data["object"]
418 end
419
420 test "it rejects posts from other users", %{conn: conn} do
421 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
422 user = insert(:user)
423 otheruser = insert(:user)
424
425 conn =
426 conn
427 |> assign(:user, otheruser)
428 |> put_req_header("content-type", "application/activity+json")
429 |> post("/users/#{user.nickname}/outbox", data)
430
431 assert json_response(conn, 403)
432 end
433
434 test "it inserts an incoming create activity into the database", %{conn: conn} do
435 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
436 user = insert(:user)
437
438 conn =
439 conn
440 |> assign(:user, user)
441 |> put_req_header("content-type", "application/activity+json")
442 |> post("/users/#{user.nickname}/outbox", data)
443
444 result = json_response(conn, 201)
445 assert Activity.get_by_ap_id(result["id"])
446 end
447
448 test "it rejects an incoming activity with bogus type", %{conn: conn} do
449 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
450 user = insert(:user)
451
452 data =
453 data
454 |> Map.put("type", "BadType")
455
456 conn =
457 conn
458 |> assign(:user, user)
459 |> put_req_header("content-type", "application/activity+json")
460 |> post("/users/#{user.nickname}/outbox", data)
461
462 assert json_response(conn, 400)
463 end
464
465 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
466 note_activity = insert(:note_activity)
467 user = User.get_cached_by_ap_id(note_activity.data["actor"])
468
469 data = %{
470 type: "Delete",
471 object: %{
472 id: note_activity.data["object"]["id"]
473 }
474 }
475
476 conn =
477 conn
478 |> assign(:user, user)
479 |> put_req_header("content-type", "application/activity+json")
480 |> post("/users/#{user.nickname}/outbox", data)
481
482 result = json_response(conn, 201)
483 assert Activity.get_by_ap_id(result["id"])
484
485 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
486 assert object
487 assert object.data["type"] == "Tombstone"
488 end
489
490 test "it rejects delete activity of object from other actor", %{conn: conn} do
491 note_activity = insert(:note_activity)
492 user = insert(:user)
493
494 data = %{
495 type: "Delete",
496 object: %{
497 id: note_activity.data["object"]["id"]
498 }
499 }
500
501 conn =
502 conn
503 |> assign(:user, user)
504 |> put_req_header("content-type", "application/activity+json")
505 |> post("/users/#{user.nickname}/outbox", data)
506
507 assert json_response(conn, 400)
508 end
509
510 test "it increases like count when receiving a like action", %{conn: conn} do
511 note_activity = insert(:note_activity)
512 user = User.get_cached_by_ap_id(note_activity.data["actor"])
513
514 data = %{
515 type: "Like",
516 object: %{
517 id: note_activity.data["object"]["id"]
518 }
519 }
520
521 conn =
522 conn
523 |> assign(:user, user)
524 |> put_req_header("content-type", "application/activity+json")
525 |> post("/users/#{user.nickname}/outbox", data)
526
527 result = json_response(conn, 201)
528 assert Activity.get_by_ap_id(result["id"])
529
530 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
531 assert object
532 assert object.data["like_count"] == 1
533 end
534 end
535
536 describe "/users/:nickname/followers" do
537 test "it returns the followers in a collection", %{conn: conn} do
538 user = insert(:user)
539 user_two = insert(:user)
540 User.follow(user, user_two)
541
542 result =
543 conn
544 |> get("/users/#{user_two.nickname}/followers")
545 |> json_response(200)
546
547 assert result["first"]["orderedItems"] == [user.ap_id]
548 end
549
550 test "it returns returns empty if the user has 'hide_followers' set", %{conn: conn} do
551 user = insert(:user)
552 user_two = insert(:user, %{info: %{hide_followers: true}})
553 User.follow(user, user_two)
554
555 result =
556 conn
557 |> get("/users/#{user_two.nickname}/followers")
558 |> json_response(200)
559
560 assert result["first"]["orderedItems"] == []
561 assert result["totalItems"] == 0
562 end
563
564 test "it works for more than 10 users", %{conn: conn} do
565 user = insert(:user)
566
567 Enum.each(1..15, fn _ ->
568 other_user = insert(:user)
569 User.follow(other_user, user)
570 end)
571
572 result =
573 conn
574 |> get("/users/#{user.nickname}/followers")
575 |> json_response(200)
576
577 assert length(result["first"]["orderedItems"]) == 10
578 assert result["first"]["totalItems"] == 15
579 assert result["totalItems"] == 15
580
581 result =
582 conn
583 |> get("/users/#{user.nickname}/followers?page=2")
584 |> json_response(200)
585
586 assert length(result["orderedItems"]) == 5
587 assert result["totalItems"] == 15
588 end
589 end
590
591 describe "/users/:nickname/following" do
592 test "it returns the following in a collection", %{conn: conn} do
593 user = insert(:user)
594 user_two = insert(:user)
595 User.follow(user, user_two)
596
597 result =
598 conn
599 |> get("/users/#{user.nickname}/following")
600 |> json_response(200)
601
602 assert result["first"]["orderedItems"] == [user_two.ap_id]
603 end
604
605 test "it returns returns empty if the user has 'hide_follows' set", %{conn: conn} do
606 user = insert(:user, %{info: %{hide_follows: true}})
607 user_two = insert(:user)
608 User.follow(user, user_two)
609
610 result =
611 conn
612 |> get("/users/#{user.nickname}/following")
613 |> json_response(200)
614
615 assert result["first"]["orderedItems"] == []
616 assert result["totalItems"] == 0
617 end
618
619 test "it works for more than 10 users", %{conn: conn} do
620 user = insert(:user)
621
622 Enum.each(1..15, fn _ ->
623 user = User.get_cached_by_id(user.id)
624 other_user = insert(:user)
625 User.follow(user, other_user)
626 end)
627
628 result =
629 conn
630 |> get("/users/#{user.nickname}/following")
631 |> json_response(200)
632
633 assert length(result["first"]["orderedItems"]) == 10
634 assert result["first"]["totalItems"] == 15
635 assert result["totalItems"] == 15
636
637 result =
638 conn
639 |> get("/users/#{user.nickname}/following?page=2")
640 |> json_response(200)
641
642 assert length(result["orderedItems"]) == 5
643 assert result["totalItems"] == 15
644 end
645 end
646 end