1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
6 use Pleroma.Web.ConnCase
7 use Oban.Testing, repo: Pleroma.Repo
10 alias Pleroma.Delivery
11 alias Pleroma.Instances
13 alias Pleroma.Tests.ObanHelpers
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.ActivityPub.ObjectView
17 alias Pleroma.Web.ActivityPub.Relay
18 alias Pleroma.Web.ActivityPub.UserView
19 alias Pleroma.Web.ActivityPub.Utils
20 alias Pleroma.Web.CommonAPI
21 alias Pleroma.Web.Endpoint
22 alias Pleroma.Workers.ReceiverWorker
24 import Pleroma.Factory
26 require Pleroma.Constants
29 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
33 setup do: clear_config([:instance, :federating], true)
36 setup do: clear_config([:instance, :allow_relay])
38 test "with the relay active, it returns the relay user", %{conn: conn} do
41 |> get(activity_pub_path(conn, :relay))
44 assert res["id"] =~ "/relay"
47 test "with the relay disabled, it returns 404", %{conn: conn} do
48 clear_config([:instance, :allow_relay], false)
51 |> get(activity_pub_path(conn, :relay))
55 test "on non-federating instance, it returns 404", %{conn: conn} do
56 clear_config([:instance, :federating], false)
60 |> assign(:user, user)
61 |> get(activity_pub_path(conn, :relay))
66 describe "/internal/fetch" do
67 test "it returns the internal fetch user", %{conn: conn} do
70 |> get(activity_pub_path(conn, :internal_fetch))
73 assert res["id"] =~ "/fetch"
76 test "on non-federating instance, it returns 404", %{conn: conn} do
77 clear_config([:instance, :federating], false)
81 |> assign(:user, user)
82 |> get(activity_pub_path(conn, :internal_fetch))
87 describe "/users/:nickname" do
88 test "it returns a json representation of the user with accept application/json", %{
95 |> put_req_header("accept", "application/json")
96 |> get("/users/#{user.nickname}")
98 user = User.get_cached_by_id(user.id)
100 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
103 test "it returns a json representation of the user with accept application/activity+json", %{
110 |> put_req_header("accept", "application/activity+json")
111 |> get("/users/#{user.nickname}")
113 user = User.get_cached_by_id(user.id)
115 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
118 test "it returns a json representation of the user with accept application/ld+json", %{
127 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
129 |> get("/users/#{user.nickname}")
131 user = User.get_cached_by_id(user.id)
133 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
136 test "it returns 404 for remote users", %{
139 user = insert(:user, local: false, nickname: "remoteuser@example.com")
143 |> put_req_header("accept", "application/json")
144 |> get("/users/#{user.nickname}.json")
146 assert json_response(conn, 404)
149 test "it returns error when user is not found", %{conn: conn} do
152 |> put_req_header("accept", "application/json")
153 |> get("/users/jimm")
154 |> json_response(404)
156 assert response == "Not found"
160 describe "mastodon compatibility routes" do
161 test "it returns a json representation of the object with accept application/json", %{
168 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
169 "actor" => Endpoint.url() <> "/users/raymoo",
170 "to" => [Pleroma.Constants.as_public()]
176 |> put_req_header("accept", "application/json")
177 |> get("/users/raymoo/statuses/999999999")
179 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
182 test "it returns a json representation of the activity with accept application/json", %{
189 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
190 "actor" => Endpoint.url() <> "/users/raymoo",
191 "to" => [Pleroma.Constants.as_public()]
197 "id" => object.data["id"] <> "/activity",
199 "object" => object.data["id"],
200 "actor" => object.data["actor"],
201 "to" => object.data["to"]
203 |> ActivityPub.persist(local: true)
207 |> put_req_header("accept", "application/json")
208 |> get("/users/raymoo/statuses/999999999/activity")
210 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
214 describe "/objects/:uuid" do
215 test "it doesn't return a local-only object", %{conn: conn} do
217 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
219 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
221 object = Object.normalize(post, fetch: false)
222 uuid = String.split(object.data["id"], "/") |> List.last()
226 |> put_req_header("accept", "application/json")
227 |> get("/objects/#{uuid}")
229 assert json_response(conn, 404)
232 test "returns local-only objects when authenticated", %{conn: conn} do
234 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
236 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
238 object = Object.normalize(post, fetch: false)
239 uuid = String.split(object.data["id"], "/") |> List.last()
243 |> assign(:user, user)
244 |> put_req_header("accept", "application/activity+json")
245 |> get("/objects/#{uuid}")
247 assert json_response(response, 200) == ObjectView.render("object.json", %{object: object})
250 test "it returns a json representation of the object with accept application/json", %{
254 uuid = String.split(note.data["id"], "/") |> List.last()
258 |> put_req_header("accept", "application/json")
259 |> get("/objects/#{uuid}")
261 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
264 test "it returns a json representation of the object with accept application/activity+json",
267 uuid = String.split(note.data["id"], "/") |> List.last()
271 |> put_req_header("accept", "application/activity+json")
272 |> get("/objects/#{uuid}")
274 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
277 test "it returns a json representation of the object with accept application/ld+json", %{
281 uuid = String.split(note.data["id"], "/") |> List.last()
287 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
289 |> get("/objects/#{uuid}")
291 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
294 test "it returns 404 for non-public messages", %{conn: conn} do
295 note = insert(:direct_note)
296 uuid = String.split(note.data["id"], "/") |> List.last()
300 |> put_req_header("accept", "application/activity+json")
301 |> get("/objects/#{uuid}")
303 assert json_response(conn, 404)
306 test "returns visible non-public messages when authenticated", %{conn: conn} do
307 note = insert(:direct_note)
308 uuid = String.split(note.data["id"], "/") |> List.last()
309 user = User.get_by_ap_id(note.data["actor"])
310 marisa = insert(:user)
313 |> assign(:user, marisa)
314 |> put_req_header("accept", "application/activity+json")
315 |> get("/objects/#{uuid}")
316 |> json_response(404)
320 |> assign(:user, user)
321 |> put_req_header("accept", "application/activity+json")
322 |> get("/objects/#{uuid}")
323 |> json_response(200)
325 assert response == ObjectView.render("object.json", %{object: note})
328 test "it returns 404 for tombstone objects", %{conn: conn} do
329 tombstone = insert(:tombstone)
330 uuid = String.split(tombstone.data["id"], "/") |> List.last()
334 |> put_req_header("accept", "application/activity+json")
335 |> get("/objects/#{uuid}")
337 assert json_response(conn, 404)
340 test "it caches a response", %{conn: conn} do
342 uuid = String.split(note.data["id"], "/") |> List.last()
346 |> put_req_header("accept", "application/activity+json")
347 |> get("/objects/#{uuid}")
349 assert json_response(conn1, :ok)
350 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
354 |> put_req_header("accept", "application/activity+json")
355 |> get("/objects/#{uuid}")
357 assert json_response(conn1, :ok) == json_response(conn2, :ok)
358 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
361 test "cached purged after object deletion", %{conn: conn} do
363 uuid = String.split(note.data["id"], "/") |> List.last()
367 |> put_req_header("accept", "application/activity+json")
368 |> get("/objects/#{uuid}")
370 assert json_response(conn1, :ok)
371 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
377 |> put_req_header("accept", "application/activity+json")
378 |> get("/objects/#{uuid}")
380 assert "Not found" == json_response(conn2, :not_found)
384 describe "/activities/:uuid" do
385 test "it doesn't return a local-only activity", %{conn: conn} do
387 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
389 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
391 uuid = String.split(post.data["id"], "/") |> List.last()
395 |> put_req_header("accept", "application/json")
396 |> get("/activities/#{uuid}")
398 assert json_response(conn, 404)
401 test "returns local-only activities when authenticated", %{conn: conn} do
403 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
405 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
407 uuid = String.split(post.data["id"], "/") |> List.last()
411 |> assign(:user, user)
412 |> put_req_header("accept", "application/activity+json")
413 |> get("/activities/#{uuid}")
415 assert json_response(response, 200) == ObjectView.render("object.json", %{object: post})
418 test "it returns a json representation of the activity", %{conn: conn} do
419 activity = insert(:note_activity)
420 uuid = String.split(activity.data["id"], "/") |> List.last()
424 |> put_req_header("accept", "application/activity+json")
425 |> get("/activities/#{uuid}")
427 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
430 test "it returns 404 for non-public activities", %{conn: conn} do
431 activity = insert(:direct_note_activity)
432 uuid = String.split(activity.data["id"], "/") |> List.last()
436 |> put_req_header("accept", "application/activity+json")
437 |> get("/activities/#{uuid}")
439 assert json_response(conn, 404)
442 test "returns visible non-public messages when authenticated", %{conn: conn} do
443 note = insert(:direct_note_activity)
444 uuid = String.split(note.data["id"], "/") |> List.last()
445 user = User.get_by_ap_id(note.data["actor"])
446 marisa = insert(:user)
449 |> assign(:user, marisa)
450 |> put_req_header("accept", "application/activity+json")
451 |> get("/activities/#{uuid}")
452 |> json_response(404)
456 |> assign(:user, user)
457 |> put_req_header("accept", "application/activity+json")
458 |> get("/activities/#{uuid}")
459 |> json_response(200)
461 assert response == ObjectView.render("object.json", %{object: note})
464 test "it caches a response", %{conn: conn} do
465 activity = insert(:note_activity)
466 uuid = String.split(activity.data["id"], "/") |> List.last()
470 |> put_req_header("accept", "application/activity+json")
471 |> get("/activities/#{uuid}")
473 assert json_response(conn1, :ok)
474 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
478 |> put_req_header("accept", "application/activity+json")
479 |> get("/activities/#{uuid}")
481 assert json_response(conn1, :ok) == json_response(conn2, :ok)
482 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
485 test "cached purged after activity deletion", %{conn: conn} do
487 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
489 uuid = String.split(activity.data["id"], "/") |> List.last()
493 |> put_req_header("accept", "application/activity+json")
494 |> get("/activities/#{uuid}")
496 assert json_response(conn1, :ok)
497 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
499 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
503 |> put_req_header("accept", "application/activity+json")
504 |> get("/activities/#{uuid}")
506 assert "Not found" == json_response(conn2, :not_found)
511 test "it inserts an incoming activity into the database", %{conn: conn} do
512 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
516 |> assign(:valid_signature, true)
517 |> put_req_header("content-type", "application/activity+json")
518 |> post("/inbox", data)
520 assert "ok" == json_response(conn, 200)
522 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
523 assert Activity.get_by_ap_id(data["id"])
526 @tag capture_log: true
527 test "it inserts an incoming activity into the database" <>
528 "even if we can't fetch the user but have it in our db",
532 ap_id: "https://mastodon.example.org/users/raymoo",
535 last_refreshed_at: nil
539 File.read!("test/fixtures/mastodon-post-activity.json")
541 |> Map.put("actor", user.ap_id)
542 |> put_in(["object", "attributedTo"], user.ap_id)
546 |> assign(:valid_signature, true)
547 |> put_req_header("content-type", "application/activity+json")
548 |> post("/inbox", data)
550 assert "ok" == json_response(conn, 200)
552 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
553 assert Activity.get_by_ap_id(data["id"])
556 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
557 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
559 sender_url = data["actor"]
560 Instances.set_consistently_unreachable(sender_url)
561 refute Instances.reachable?(sender_url)
565 |> assign(:valid_signature, true)
566 |> put_req_header("content-type", "application/activity+json")
567 |> post("/inbox", data)
569 assert "ok" == json_response(conn, 200)
570 assert Instances.reachable?(sender_url)
573 test "accept follow activity", %{conn: conn} do
574 clear_config([:instance, :federating], true)
575 relay = Relay.get_actor()
577 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
579 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
580 relay = refresh_record(relay)
583 File.read!("test/fixtures/relay/accept-follow.json")
584 |> String.replace("{{ap_id}}", relay.ap_id)
585 |> String.replace("{{activity_id}}", activity.data["id"])
589 |> assign(:valid_signature, true)
590 |> put_req_header("content-type", "application/activity+json")
591 |> post("/inbox", accept)
592 |> json_response(200)
594 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
596 assert Pleroma.FollowingRelationship.following?(
601 Mix.shell(Mix.Shell.Process)
604 Mix.shell(Mix.Shell.IO)
607 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
608 assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
611 @tag capture_log: true
612 test "without valid signature, " <>
613 "it only accepts Create activities and requires enabled federation",
615 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
616 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
618 conn = put_req_header(conn, "content-type", "application/activity+json")
620 clear_config([:instance, :federating], false)
623 |> post("/inbox", data)
624 |> json_response(403)
627 |> post("/inbox", non_create_data)
628 |> json_response(403)
630 clear_config([:instance, :federating], true)
632 ret_conn = post(conn, "/inbox", data)
633 assert "ok" == json_response(ret_conn, 200)
636 |> post("/inbox", non_create_data)
637 |> json_response(400)
641 describe "/users/:nickname/inbox" do
644 File.read!("test/fixtures/mastodon-post-activity.json")
650 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
655 |> Map.put("bcc", [user.ap_id])
656 |> Kernel.put_in(["object", "bcc"], [user.ap_id])
660 |> assign(:valid_signature, true)
661 |> put_req_header("content-type", "application/activity+json")
662 |> post("/users/#{user.nickname}/inbox", data)
664 assert "ok" == json_response(conn, 200)
665 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
666 assert Activity.get_by_ap_id(data["id"])
669 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
674 |> Map.put("to", user.ap_id)
676 |> Kernel.put_in(["object", "to"], user.ap_id)
677 |> Kernel.put_in(["object", "cc"], [])
681 |> assign(:valid_signature, true)
682 |> put_req_header("content-type", "application/activity+json")
683 |> post("/users/#{user.nickname}/inbox", data)
685 assert "ok" == json_response(conn, 200)
686 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
687 assert Activity.get_by_ap_id(data["id"])
690 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
696 |> Map.put("cc", user.ap_id)
697 |> Kernel.put_in(["object", "to"], [])
698 |> Kernel.put_in(["object", "cc"], user.ap_id)
702 |> assign(:valid_signature, true)
703 |> put_req_header("content-type", "application/activity+json")
704 |> post("/users/#{user.nickname}/inbox", data)
706 assert "ok" == json_response(conn, 200)
707 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
708 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
709 assert user.ap_id in activity.recipients
712 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
719 |> Map.put("bcc", user.ap_id)
720 |> Kernel.put_in(["object", "to"], [])
721 |> Kernel.put_in(["object", "cc"], [])
722 |> Kernel.put_in(["object", "bcc"], user.ap_id)
726 |> assign(:valid_signature, true)
727 |> put_req_header("content-type", "application/activity+json")
728 |> post("/users/#{user.nickname}/inbox", data)
730 assert "ok" == json_response(conn, 200)
731 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
732 assert Activity.get_by_ap_id(data["id"])
735 test "it accepts announces with to as string instead of array", %{conn: conn} do
738 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
739 announcer = insert(:user, local: false)
742 "@context" => "https://www.w3.org/ns/activitystreams",
743 "actor" => announcer.ap_id,
744 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
745 "object" => post.data["object"],
746 "to" => "https://www.w3.org/ns/activitystreams#Public",
747 "cc" => [user.ap_id],
753 |> assign(:valid_signature, true)
754 |> put_req_header("content-type", "application/activity+json")
755 |> post("/users/#{user.nickname}/inbox", data)
757 assert "ok" == json_response(conn, 200)
758 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
759 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
760 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
763 test "it accepts messages from actors that are followed by the user", %{
767 recipient = insert(:user)
768 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
770 {:ok, recipient, actor} = User.follow(recipient, actor)
774 |> Map.put("attributedTo", actor.ap_id)
778 |> Map.put("actor", actor.ap_id)
779 |> Map.put("object", object)
783 |> assign(:valid_signature, true)
784 |> put_req_header("content-type", "application/activity+json")
785 |> post("/users/#{recipient.nickname}/inbox", data)
787 assert "ok" == json_response(conn, 200)
788 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
789 assert Activity.get_by_ap_id(data["id"])
792 test "it rejects reads from other users", %{conn: conn} do
794 other_user = insert(:user)
798 |> assign(:user, other_user)
799 |> put_req_header("accept", "application/activity+json")
800 |> get("/users/#{user.nickname}/inbox")
802 assert json_response(conn, 403)
805 test "it returns a note activity in a collection", %{conn: conn} do
806 note_activity = insert(:direct_note_activity)
807 note_object = Object.normalize(note_activity, fetch: false)
808 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
812 |> assign(:user, user)
813 |> put_req_header("accept", "application/activity+json")
814 |> get("/users/#{user.nickname}/inbox?page=true")
816 assert response(conn, 200) =~ note_object.data["content"]
819 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
821 data = Map.put(data, "bcc", [user.ap_id])
823 sender_host = URI.parse(data["actor"]).host
824 Instances.set_consistently_unreachable(sender_host)
825 refute Instances.reachable?(sender_host)
829 |> assign(:valid_signature, true)
830 |> put_req_header("content-type", "application/activity+json")
831 |> post("/users/#{user.nickname}/inbox", data)
833 assert "ok" == json_response(conn, 200)
834 assert Instances.reachable?(sender_host)
837 @tag capture_log: true
838 test "it removes all follower collections but actor's", %{conn: conn} do
839 [actor, recipient] = insert_pair(:user)
843 recipient.follower_address,
844 "https://www.w3.org/ns/activitystreams#Public"
847 cc = [recipient.follower_address, actor.follower_address]
850 "@context" => ["https://www.w3.org/ns/activitystreams"],
852 "id" => Utils.generate_activity_id(),
855 "actor" => actor.ap_id,
860 "content" => "It's a note",
861 "attributedTo" => actor.ap_id,
862 "id" => Utils.generate_object_id()
867 |> assign(:valid_signature, true)
868 |> put_req_header("content-type", "application/activity+json")
869 |> post("/users/#{recipient.nickname}/inbox", data)
870 |> json_response(200)
872 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
874 assert activity = Activity.get_by_ap_id(data["id"])
877 assert actor.follower_address in activity.recipients
878 assert actor.follower_address in activity.data["cc"]
880 refute recipient.follower_address in activity.recipients
881 refute recipient.follower_address in activity.data["cc"]
882 refute recipient.follower_address in activity.data["to"]
885 test "it requires authentication", %{conn: conn} do
887 conn = put_req_header(conn, "accept", "application/activity+json")
889 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
890 assert json_response(ret_conn, 403)
894 |> assign(:user, user)
895 |> get("/users/#{user.nickname}/inbox")
897 assert json_response(ret_conn, 200)
900 @tag capture_log: true
901 test "forwarded report", %{conn: conn} do
902 admin = insert(:user, is_admin: true)
903 actor = insert(:user, local: false)
904 remote_domain = URI.parse(actor.ap_id).host
905 reported_user = insert(:user)
907 note = insert(:note_activity, user: reported_user)
911 "https://www.w3.org/ns/activitystreams",
912 "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
917 "actor" => actor.ap_id,
922 "context" => "context",
923 "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
924 "nickname" => reported_user.nickname,
929 "actor_type" => "Person",
930 "approval_pending" => false,
932 "confirmation_pending" => false,
933 "deactivated" => false,
934 "display_name" => "test user",
935 "id" => reported_user.id,
937 "nickname" => reported_user.nickname,
938 "registration_reason" => nil,
944 "url" => reported_user.ap_id
947 "id" => note.data["id"],
948 "published" => note.data["published"],
952 "published" => note.data["published"],
959 |> assign(:valid_signature, true)
960 |> put_req_header("content-type", "application/activity+json")
961 |> post("/users/#{reported_user.nickname}/inbox", data)
962 |> json_response(200)
964 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
966 assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
968 ObanHelpers.perform_all()
970 Swoosh.TestAssertions.assert_email_sent(
971 to: {admin.name, admin.email},
972 html_body: ~r/Reported Account:/i
976 @tag capture_log: true
977 test "forwarded report from mastodon", %{conn: conn} do
978 admin = insert(:user, is_admin: true)
979 actor = insert(:user, local: false)
980 remote_domain = URI.parse(actor.ap_id).host
981 remote_actor = "https://#{remote_domain}/actor"
982 [reported_user, another] = insert_list(2, :user)
984 note = insert(:note_activity, user: reported_user)
986 Pleroma.Web.CommonAPI.favorite(another, note.id)
989 "test/fixtures/mastodon/application_actor.json"
991 |> String.replace("{{DOMAIN}}", remote_domain)
993 Tesla.Mock.mock(fn %{url: ^remote_actor} ->
996 body: mock_json_body,
997 headers: [{"content-type", "application/activity+json"}]
1002 "@context" => "https://www.w3.org/ns/activitystreams",
1003 "actor" => remote_actor,
1004 "content" => "test report",
1005 "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
1006 "nickname" => reported_user.nickname,
1008 reported_user.ap_id,
1015 |> assign(:valid_signature, true)
1016 |> put_req_header("content-type", "application/activity+json")
1017 |> post("/users/#{reported_user.nickname}/inbox", data)
1018 |> json_response(200)
1020 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1022 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1023 reported_user_ap_id = reported_user.ap_id
1025 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1027 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1028 ObanHelpers.perform_all()
1030 Swoosh.TestAssertions.assert_email_sent(
1031 to: {admin.name, admin.email},
1032 html_body: ~r/#{note.data["object"]}/i
1037 describe "GET /users/:nickname/outbox" do
1038 test "it paginates correctly", %{conn: conn} do
1039 user = insert(:user)
1040 conn = assign(conn, :user, user)
1041 outbox_endpoint = user.ap_id <> "/outbox"
1045 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1051 |> put_req_header("accept", "application/activity+json")
1052 |> get(outbox_endpoint <> "?page=true")
1053 |> json_response(200)
1055 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1056 assert length(result["orderedItems"]) == 20
1057 assert length(result_ids) == 20
1058 assert result["next"]
1059 assert String.starts_with?(result["next"], outbox_endpoint)
1063 |> put_req_header("accept", "application/activity+json")
1064 |> get(result["next"])
1065 |> json_response(200)
1067 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1068 assert length(result_next["orderedItems"]) == 6
1069 assert length(result_next_ids) == 6
1070 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1071 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1072 assert String.starts_with?(result["id"], outbox_endpoint)
1076 |> put_req_header("accept", "application/activity+json")
1077 |> get(result_next["id"])
1078 |> json_response(200)
1080 assert result_next == result_next_again
1083 test "it returns 200 even if there're no activities", %{conn: conn} do
1084 user = insert(:user)
1085 outbox_endpoint = user.ap_id <> "/outbox"
1089 |> assign(:user, user)
1090 |> put_req_header("accept", "application/activity+json")
1091 |> get(outbox_endpoint)
1093 result = json_response(conn, 200)
1094 assert outbox_endpoint == result["id"]
1097 test "it returns a note activity in a collection", %{conn: conn} do
1098 note_activity = insert(:note_activity)
1099 note_object = Object.normalize(note_activity, fetch: false)
1100 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1104 |> assign(:user, user)
1105 |> put_req_header("accept", "application/activity+json")
1106 |> get("/users/#{user.nickname}/outbox?page=true")
1108 assert response(conn, 200) =~ note_object.data["content"]
1111 test "it returns an announce activity in a collection", %{conn: conn} do
1112 announce_activity = insert(:announce_activity)
1113 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1117 |> assign(:user, user)
1118 |> put_req_header("accept", "application/activity+json")
1119 |> get("/users/#{user.nickname}/outbox?page=true")
1121 assert response(conn, 200) =~ announce_activity.data["object"]
1124 test "It returns poll Answers when authenticated", %{conn: conn} do
1125 poller = insert(:user)
1126 voter = insert(:user)
1129 CommonAPI.post(poller, %{
1131 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1134 assert question = Object.normalize(activity, fetch: false)
1136 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1140 |> assign(:user, voter)
1141 |> put_req_header("accept", "application/activity+json")
1142 |> get(voter.ap_id <> "/outbox?page=true")
1143 |> json_response(200)
1145 assert [answer_outbox] = outbox_get["orderedItems"]
1146 assert answer_outbox["id"] == activity.data["id"]
1150 describe "POST /users/:nickname/outbox (C2S)" do
1151 setup do: clear_config([:instance, :limit])
1156 "@context" => "https://www.w3.org/ns/activitystreams",
1158 "object" => %{"type" => "Note", "content" => "AP C2S test"},
1159 "to" => "https://www.w3.org/ns/activitystreams#Public",
1165 test "it rejects posts from other users / unauthenticated users", %{
1169 user = insert(:user)
1170 other_user = insert(:user)
1171 conn = put_req_header(conn, "content-type", "application/activity+json")
1174 |> post("/users/#{user.nickname}/outbox", activity)
1175 |> json_response(403)
1178 |> assign(:user, other_user)
1179 |> post("/users/#{user.nickname}/outbox", activity)
1180 |> json_response(403)
1183 test "it inserts an incoming create activity into the database", %{
1187 user = insert(:user)
1191 |> assign(:user, user)
1192 |> put_req_header("content-type", "application/activity+json")
1193 |> post("/users/#{user.nickname}/outbox", activity)
1194 |> json_response(201)
1196 assert Activity.get_by_ap_id(result["id"])
1197 assert result["object"]
1198 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1199 assert object["content"] == activity["object"]["content"]
1202 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1203 user = insert(:user)
1207 |> put_in(["object", "type"], "Benis")
1211 |> assign(:user, user)
1212 |> put_req_header("content-type", "application/activity+json")
1213 |> post("/users/#{user.nickname}/outbox", activity)
1214 |> json_response(400)
1217 test "it inserts an incoming sensitive activity into the database", %{
1221 user = insert(:user)
1222 conn = assign(conn, :user, user)
1223 object = Map.put(activity["object"], "sensitive", true)
1224 activity = Map.put(activity, "object", object)
1228 |> put_req_header("content-type", "application/activity+json")
1229 |> post("/users/#{user.nickname}/outbox", activity)
1230 |> json_response(201)
1232 assert Activity.get_by_ap_id(response["id"])
1233 assert response["object"]
1234 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1235 assert response_object["sensitive"] == true
1236 assert response_object["content"] == activity["object"]["content"]
1240 |> put_req_header("accept", "application/activity+json")
1241 |> get(response["id"])
1242 |> json_response(200)
1244 assert representation["object"]["sensitive"] == true
1247 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1248 user = insert(:user)
1249 activity = Map.put(activity, "type", "BadType")
1253 |> assign(:user, user)
1254 |> put_req_header("content-type", "application/activity+json")
1255 |> post("/users/#{user.nickname}/outbox", activity)
1257 assert json_response(conn, 400)
1260 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1261 note_activity = insert(:note_activity)
1262 note_object = Object.normalize(note_activity, fetch: false)
1263 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1268 id: note_object.data["id"]
1274 |> assign(:user, user)
1275 |> put_req_header("content-type", "application/activity+json")
1276 |> post("/users/#{user.nickname}/outbox", data)
1278 result = json_response(conn, 201)
1279 assert Activity.get_by_ap_id(result["id"])
1281 assert object = Object.get_by_ap_id(note_object.data["id"])
1282 assert object.data["type"] == "Tombstone"
1285 test "it rejects delete activity of object from other actor", %{conn: conn} do
1286 note_activity = insert(:note_activity)
1287 note_object = Object.normalize(note_activity, fetch: false)
1288 user = insert(:user)
1293 id: note_object.data["id"]
1299 |> assign(:user, user)
1300 |> put_req_header("content-type", "application/activity+json")
1301 |> post("/users/#{user.nickname}/outbox", data)
1303 assert json_response(conn, 400)
1306 test "it increases like count when receiving a like action", %{conn: conn} do
1307 note_activity = insert(:note_activity)
1308 note_object = Object.normalize(note_activity, fetch: false)
1309 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1314 id: note_object.data["id"]
1320 |> assign(:user, user)
1321 |> put_req_header("content-type", "application/activity+json")
1322 |> post("/users/#{user.nickname}/outbox", data)
1324 result = json_response(conn, 201)
1325 assert Activity.get_by_ap_id(result["id"])
1327 assert object = Object.get_by_ap_id(note_object.data["id"])
1328 assert object.data["like_count"] == 1
1331 test "it doesn't spreads faulty attributedTo or actor fields", %{
1335 reimu = insert(:user, nickname: "reimu")
1336 cirno = insert(:user, nickname: "cirno")
1343 |> put_in(["object", "actor"], reimu.ap_id)
1344 |> put_in(["object", "attributedTo"], reimu.ap_id)
1345 |> put_in(["actor"], reimu.ap_id)
1346 |> put_in(["attributedTo"], reimu.ap_id)
1350 |> assign(:user, cirno)
1351 |> put_req_header("content-type", "application/activity+json")
1352 |> post("/users/#{reimu.nickname}/outbox", activity)
1353 |> json_response(403)
1357 |> assign(:user, cirno)
1358 |> put_req_header("content-type", "application/activity+json")
1359 |> post("/users/#{cirno.nickname}/outbox", activity)
1360 |> json_response(201)
1362 assert cirno_outbox["attributedTo"] == nil
1363 assert cirno_outbox["actor"] == cirno.ap_id
1365 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1366 assert cirno_object.data["actor"] == cirno.ap_id
1367 assert cirno_object.data["attributedTo"] == cirno.ap_id
1370 test "Character limitation", %{conn: conn, activity: activity} do
1371 clear_config([:instance, :limit], 5)
1372 user = insert(:user)
1376 |> assign(:user, user)
1377 |> put_req_header("content-type", "application/activity+json")
1378 |> post("/users/#{user.nickname}/outbox", activity)
1379 |> json_response(400)
1381 assert result == "Note is over the character limit"
1385 describe "/relay/followers" do
1386 test "it returns relay followers", %{conn: conn} do
1387 relay_actor = Relay.get_actor()
1388 user = insert(:user)
1389 User.follow(user, relay_actor)
1393 |> get("/relay/followers")
1394 |> json_response(200)
1396 assert result["first"]["orderedItems"] == [user.ap_id]
1399 test "on non-federating instance, it returns 404", %{conn: conn} do
1400 clear_config([:instance, :federating], false)
1401 user = insert(:user)
1404 |> assign(:user, user)
1405 |> get("/relay/followers")
1406 |> json_response(404)
1410 describe "/relay/following" do
1411 test "it returns relay following", %{conn: conn} do
1414 |> get("/relay/following")
1415 |> json_response(200)
1417 assert result["first"]["orderedItems"] == []
1420 test "on non-federating instance, it returns 404", %{conn: conn} do
1421 clear_config([:instance, :federating], false)
1422 user = insert(:user)
1425 |> assign(:user, user)
1426 |> get("/relay/following")
1427 |> json_response(404)
1431 describe "/users/:nickname/followers" do
1432 test "it returns the followers in a collection", %{conn: conn} do
1433 user = insert(:user)
1434 user_two = insert(:user)
1435 User.follow(user, user_two)
1439 |> assign(:user, user_two)
1440 |> get("/users/#{user_two.nickname}/followers")
1441 |> json_response(200)
1443 assert result["first"]["orderedItems"] == [user.ap_id]
1446 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1447 user = insert(:user)
1448 user_two = insert(:user, hide_followers: true)
1449 User.follow(user, user_two)
1453 |> assign(:user, user)
1454 |> get("/users/#{user_two.nickname}/followers")
1455 |> json_response(200)
1457 assert is_binary(result["first"])
1460 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1462 user = insert(:user)
1463 other_user = insert(:user, hide_followers: true)
1467 |> assign(:user, user)
1468 |> get("/users/#{other_user.nickname}/followers?page=1")
1470 assert result.status == 403
1471 assert result.resp_body == ""
1474 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1476 user = insert(:user, hide_followers: true)
1477 other_user = insert(:user)
1478 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1482 |> assign(:user, user)
1483 |> get("/users/#{user.nickname}/followers?page=1")
1484 |> json_response(200)
1486 assert result["totalItems"] == 1
1487 assert result["orderedItems"] == [other_user.ap_id]
1490 test "it works for more than 10 users", %{conn: conn} do
1491 user = insert(:user)
1493 Enum.each(1..15, fn _ ->
1494 other_user = insert(:user)
1495 User.follow(other_user, user)
1500 |> assign(:user, user)
1501 |> get("/users/#{user.nickname}/followers")
1502 |> json_response(200)
1504 assert length(result["first"]["orderedItems"]) == 10
1505 assert result["first"]["totalItems"] == 15
1506 assert result["totalItems"] == 15
1510 |> assign(:user, user)
1511 |> get("/users/#{user.nickname}/followers?page=2")
1512 |> json_response(200)
1514 assert length(result["orderedItems"]) == 5
1515 assert result["totalItems"] == 15
1518 test "does not require authentication", %{conn: conn} do
1519 user = insert(:user)
1522 |> get("/users/#{user.nickname}/followers")
1523 |> json_response(200)
1527 describe "/users/:nickname/following" do
1528 test "it returns the following in a collection", %{conn: conn} do
1529 user = insert(:user)
1530 user_two = insert(:user)
1531 User.follow(user, user_two)
1535 |> assign(:user, user)
1536 |> get("/users/#{user.nickname}/following")
1537 |> json_response(200)
1539 assert result["first"]["orderedItems"] == [user_two.ap_id]
1542 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1543 user = insert(:user)
1544 user_two = insert(:user, hide_follows: true)
1545 User.follow(user, user_two)
1549 |> assign(:user, user)
1550 |> get("/users/#{user_two.nickname}/following")
1551 |> json_response(200)
1553 assert is_binary(result["first"])
1556 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1558 user = insert(:user)
1559 user_two = insert(:user, hide_follows: true)
1563 |> assign(:user, user)
1564 |> get("/users/#{user_two.nickname}/following?page=1")
1566 assert result.status == 403
1567 assert result.resp_body == ""
1570 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1572 user = insert(:user, hide_follows: true)
1573 other_user = insert(:user)
1574 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1578 |> assign(:user, user)
1579 |> get("/users/#{user.nickname}/following?page=1")
1580 |> json_response(200)
1582 assert result["totalItems"] == 1
1583 assert result["orderedItems"] == [other_user.ap_id]
1586 test "it works for more than 10 users", %{conn: conn} do
1587 user = insert(:user)
1589 Enum.each(1..15, fn _ ->
1590 user = User.get_cached_by_id(user.id)
1591 other_user = insert(:user)
1592 User.follow(user, other_user)
1597 |> assign(:user, user)
1598 |> get("/users/#{user.nickname}/following")
1599 |> json_response(200)
1601 assert length(result["first"]["orderedItems"]) == 10
1602 assert result["first"]["totalItems"] == 15
1603 assert result["totalItems"] == 15
1607 |> assign(:user, user)
1608 |> get("/users/#{user.nickname}/following?page=2")
1609 |> json_response(200)
1611 assert length(result["orderedItems"]) == 5
1612 assert result["totalItems"] == 15
1615 test "does not require authentication", %{conn: conn} do
1616 user = insert(:user)
1619 |> get("/users/#{user.nickname}/following")
1620 |> json_response(200)
1624 describe "delivery tracking" do
1625 test "it tracks a signed object fetch", %{conn: conn} do
1626 user = insert(:user, local: false)
1627 activity = insert(:note_activity)
1628 object = Object.normalize(activity, fetch: false)
1630 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1633 |> put_req_header("accept", "application/activity+json")
1634 |> assign(:user, user)
1636 |> json_response(200)
1638 assert Delivery.get(object.id, user.id)
1641 test "it tracks a signed activity fetch", %{conn: conn} do
1642 user = insert(:user, local: false)
1643 activity = insert(:note_activity)
1644 object = Object.normalize(activity, fetch: false)
1646 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1649 |> put_req_header("accept", "application/activity+json")
1650 |> assign(:user, user)
1651 |> get(activity_path)
1652 |> json_response(200)
1654 assert Delivery.get(object.id, user.id)
1657 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1658 user = insert(:user, local: false)
1659 other_user = insert(:user, local: false)
1660 activity = insert(:note_activity)
1661 object = Object.normalize(activity, fetch: false)
1663 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1666 |> put_req_header("accept", "application/activity+json")
1667 |> assign(:user, user)
1669 |> json_response(200)
1672 |> put_req_header("accept", "application/activity+json")
1673 |> assign(:user, other_user)
1675 |> json_response(200)
1677 assert Delivery.get(object.id, user.id)
1678 assert Delivery.get(object.id, other_user.id)
1681 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1682 user = insert(:user, local: false)
1683 other_user = insert(:user, local: false)
1684 activity = insert(:note_activity)
1685 object = Object.normalize(activity, fetch: false)
1687 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1690 |> put_req_header("accept", "application/activity+json")
1691 |> assign(:user, user)
1692 |> get(activity_path)
1693 |> json_response(200)
1696 |> put_req_header("accept", "application/activity+json")
1697 |> assign(:user, other_user)
1698 |> get(activity_path)
1699 |> json_response(200)
1701 assert Delivery.get(object.id, user.id)
1702 assert Delivery.get(object.id, other_user.id)
1706 describe "Additional ActivityPub C2S endpoints" do
1707 test "GET /api/ap/whoami", %{conn: conn} do
1708 user = insert(:user)
1712 |> assign(:user, user)
1713 |> get("/api/ap/whoami")
1715 user = User.get_cached_by_id(user.id)
1717 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1720 |> get("/api/ap/whoami")
1721 |> json_response(403)
1724 setup do: clear_config([:media_proxy])
1725 setup do: clear_config([Pleroma.Upload])
1727 test "POST /api/ap/upload_media", %{conn: conn} do
1728 user = insert(:user)
1730 desc = "Description of the image"
1732 image = %Plug.Upload{
1733 content_type: "image/jpeg",
1734 path: Path.absname("test/fixtures/image.jpg"),
1735 filename: "an_image.jpg"
1740 |> assign(:user, user)
1741 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1742 |> json_response(:created)
1744 assert object["name"] == desc
1745 assert object["type"] == "Document"
1746 assert object["actor"] == user.ap_id
1747 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1748 assert is_binary(object_href)
1749 assert object_mediatype == "image/jpeg"
1750 assert String.ends_with?(object_href, ".jpg")
1752 activity_request = %{
1753 "@context" => "https://www.w3.org/ns/activitystreams",
1757 "content" => "AP C2S test, attachment",
1758 "attachment" => [object]
1760 "to" => "https://www.w3.org/ns/activitystreams#Public",
1766 |> assign(:user, user)
1767 |> post("/users/#{user.nickname}/outbox", activity_request)
1768 |> json_response(:created)
1770 assert activity_response["id"]
1771 assert activity_response["object"]
1772 assert activity_response["actor"] == user.ap_id
1774 assert %Object{data: %{"attachment" => [attachment]}} =
1775 Object.normalize(activity_response["object"], fetch: false)
1777 assert attachment["type"] == "Document"
1778 assert attachment["name"] == desc
1782 "href" => ^object_href,
1784 "mediaType" => ^object_mediatype
1786 ] = attachment["url"]
1788 # Fails if unauthenticated
1790 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1791 |> json_response(403)