Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
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.

2002 lines
60KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
  5. use Pleroma.Web.ConnCase
  6. use Oban.Testing, repo: Pleroma.Repo
  7. alias Pleroma.Activity
  8. alias Pleroma.Delivery
  9. alias Pleroma.Instances
  10. alias Pleroma.Object
  11. alias Pleroma.Tests.ObanHelpers
  12. alias Pleroma.User
  13. alias Pleroma.Web.ActivityPub.ActivityPub
  14. alias Pleroma.Web.ActivityPub.ObjectView
  15. alias Pleroma.Web.ActivityPub.Relay
  16. alias Pleroma.Web.ActivityPub.UserView
  17. alias Pleroma.Web.ActivityPub.Utils
  18. alias Pleroma.Web.CommonAPI
  19. alias Pleroma.Web.Endpoint
  20. alias Pleroma.Workers.ReceiverWorker
  21. import Pleroma.Factory
  22. require Pleroma.Constants
  23. setup_all do
  24. Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
  25. :ok
  26. end
  27. setup do: clear_config([:instance, :federating], true)
  28. describe "/relay" do
  29. setup do: clear_config([:instance, :allow_relay])
  30. test "with the relay active, it returns the relay user", %{conn: conn} do
  31. res =
  32. conn
  33. |> get(activity_pub_path(conn, :relay))
  34. |> json_response(200)
  35. assert res["id"] =~ "/relay"
  36. end
  37. test "with the relay disabled, it returns 404", %{conn: conn} do
  38. clear_config([:instance, :allow_relay], false)
  39. conn
  40. |> get(activity_pub_path(conn, :relay))
  41. |> json_response(404)
  42. end
  43. test "on non-federating instance, it returns 404", %{conn: conn} do
  44. clear_config([:instance, :federating], false)
  45. user = insert(:user)
  46. conn
  47. |> assign(:user, user)
  48. |> get(activity_pub_path(conn, :relay))
  49. |> json_response(404)
  50. end
  51. end
  52. describe "/internal/fetch" do
  53. test "it returns the internal fetch user", %{conn: conn} do
  54. res =
  55. conn
  56. |> get(activity_pub_path(conn, :internal_fetch))
  57. |> json_response(200)
  58. assert res["id"] =~ "/fetch"
  59. end
  60. test "on non-federating instance, it returns 404", %{conn: conn} do
  61. clear_config([:instance, :federating], false)
  62. user = insert(:user)
  63. conn
  64. |> assign(:user, user)
  65. |> get(activity_pub_path(conn, :internal_fetch))
  66. |> json_response(404)
  67. end
  68. end
  69. describe "/users/:nickname" do
  70. test "it returns a json representation of the user with accept application/json", %{
  71. conn: conn
  72. } do
  73. user = insert(:user)
  74. conn =
  75. conn
  76. |> put_req_header("accept", "application/json")
  77. |> get("/users/#{user.nickname}")
  78. user = User.get_cached_by_id(user.id)
  79. assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
  80. end
  81. test "it returns a json representation of the user with accept application/activity+json", %{
  82. conn: conn
  83. } do
  84. user = insert(:user)
  85. conn =
  86. conn
  87. |> put_req_header("accept", "application/activity+json")
  88. |> get("/users/#{user.nickname}")
  89. user = User.get_cached_by_id(user.id)
  90. assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
  91. end
  92. test "it returns a json representation of the user with accept application/ld+json", %{
  93. conn: conn
  94. } do
  95. user = insert(:user)
  96. conn =
  97. conn
  98. |> put_req_header(
  99. "accept",
  100. "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
  101. )
  102. |> get("/users/#{user.nickname}")
  103. user = User.get_cached_by_id(user.id)
  104. assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
  105. end
  106. test "it returns 404 for remote users", %{
  107. conn: conn
  108. } do
  109. user = insert(:user, local: false, nickname: "remoteuser@example.com")
  110. conn =
  111. conn
  112. |> put_req_header("accept", "application/json")
  113. |> get("/users/#{user.nickname}.json")
  114. assert json_response(conn, 404)
  115. end
  116. test "it returns error when user is not found", %{conn: conn} do
  117. response =
  118. conn
  119. |> put_req_header("accept", "application/json")
  120. |> get("/users/jimm")
  121. |> json_response(404)
  122. assert response == "Not found"
  123. end
  124. end
  125. describe "mastodon compatibility routes" do
  126. test "it returns a json representation of the object with accept application/json", %{
  127. conn: conn
  128. } do
  129. {:ok, object} =
  130. %{
  131. "type" => "Note",
  132. "content" => "hey",
  133. "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
  134. "actor" => Endpoint.url() <> "/users/raymoo",
  135. "to" => [Pleroma.Constants.as_public()]
  136. }
  137. |> Object.create()
  138. conn =
  139. conn
  140. |> put_req_header("accept", "application/json")
  141. |> get("/users/raymoo/statuses/999999999")
  142. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
  143. end
  144. test "it returns a json representation of the activity with accept application/json", %{
  145. conn: conn
  146. } do
  147. {:ok, object} =
  148. %{
  149. "type" => "Note",
  150. "content" => "hey",
  151. "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
  152. "actor" => Endpoint.url() <> "/users/raymoo",
  153. "to" => [Pleroma.Constants.as_public()]
  154. }
  155. |> Object.create()
  156. {:ok, activity, _} =
  157. %{
  158. "id" => object.data["id"] <> "/activity",
  159. "type" => "Create",
  160. "object" => object.data["id"],
  161. "actor" => object.data["actor"],
  162. "to" => object.data["to"]
  163. }
  164. |> ActivityPub.persist(local: true)
  165. conn =
  166. conn
  167. |> put_req_header("accept", "application/json")
  168. |> get("/users/raymoo/statuses/999999999/activity")
  169. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
  170. end
  171. end
  172. describe "/objects/:uuid" do
  173. test "it doesn't return a local-only object", %{conn: conn} do
  174. user = insert(:user)
  175. {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
  176. assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
  177. object = Object.normalize(post, fetch: false)
  178. uuid = String.split(object.data["id"], "/") |> List.last()
  179. conn =
  180. conn
  181. |> put_req_header("accept", "application/json")
  182. |> get("/objects/#{uuid}")
  183. assert json_response(conn, 404)
  184. end
  185. test "returns local-only objects when authenticated", %{conn: conn} do
  186. user = insert(:user)
  187. {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
  188. assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
  189. object = Object.normalize(post, fetch: false)
  190. uuid = String.split(object.data["id"], "/") |> List.last()
  191. assert response =
  192. conn
  193. |> assign(:user, user)
  194. |> put_req_header("accept", "application/activity+json")
  195. |> get("/objects/#{uuid}")
  196. assert json_response(response, 200) == ObjectView.render("object.json", %{object: object})
  197. end
  198. test "it returns a json representation of the object with accept application/json", %{
  199. conn: conn
  200. } do
  201. note = insert(:note)
  202. uuid = String.split(note.data["id"], "/") |> List.last()
  203. conn =
  204. conn
  205. |> put_req_header("accept", "application/json")
  206. |> get("/objects/#{uuid}")
  207. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
  208. end
  209. test "it returns a json representation of the object with accept application/activity+json",
  210. %{conn: conn} do
  211. note = insert(:note)
  212. uuid = String.split(note.data["id"], "/") |> List.last()
  213. conn =
  214. conn
  215. |> put_req_header("accept", "application/activity+json")
  216. |> get("/objects/#{uuid}")
  217. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
  218. end
  219. test "it returns a json representation of the object with accept application/ld+json", %{
  220. conn: conn
  221. } do
  222. note = insert(:note)
  223. uuid = String.split(note.data["id"], "/") |> List.last()
  224. conn =
  225. conn
  226. |> put_req_header(
  227. "accept",
  228. "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
  229. )
  230. |> get("/objects/#{uuid}")
  231. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
  232. end
  233. test "it returns 404 for non-public messages", %{conn: conn} do
  234. note = insert(:direct_note)
  235. uuid = String.split(note.data["id"], "/") |> List.last()
  236. conn =
  237. conn
  238. |> put_req_header("accept", "application/activity+json")
  239. |> get("/objects/#{uuid}")
  240. assert json_response(conn, 404)
  241. end
  242. test "returns visible non-public messages when authenticated", %{conn: conn} do
  243. note = insert(:direct_note)
  244. uuid = String.split(note.data["id"], "/") |> List.last()
  245. user = User.get_by_ap_id(note.data["actor"])
  246. marisa = insert(:user)
  247. assert conn
  248. |> assign(:user, marisa)
  249. |> put_req_header("accept", "application/activity+json")
  250. |> get("/objects/#{uuid}")
  251. |> json_response(404)
  252. assert response =
  253. conn
  254. |> assign(:user, user)
  255. |> put_req_header("accept", "application/activity+json")
  256. |> get("/objects/#{uuid}")
  257. |> json_response(200)
  258. assert response == ObjectView.render("object.json", %{object: note})
  259. end
  260. test "it returns 404 for tombstone objects", %{conn: conn} do
  261. tombstone = insert(:tombstone)
  262. uuid = String.split(tombstone.data["id"], "/") |> List.last()
  263. conn =
  264. conn
  265. |> put_req_header("accept", "application/activity+json")
  266. |> get("/objects/#{uuid}")
  267. assert json_response(conn, 404)
  268. end
  269. test "it caches a response", %{conn: conn} do
  270. note = insert(:note)
  271. uuid = String.split(note.data["id"], "/") |> List.last()
  272. conn1 =
  273. conn
  274. |> put_req_header("accept", "application/activity+json")
  275. |> get("/objects/#{uuid}")
  276. assert json_response(conn1, :ok)
  277. assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
  278. conn2 =
  279. conn
  280. |> put_req_header("accept", "application/activity+json")
  281. |> get("/objects/#{uuid}")
  282. assert json_response(conn1, :ok) == json_response(conn2, :ok)
  283. assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
  284. end
  285. test "cached purged after object deletion", %{conn: conn} do
  286. note = insert(:note)
  287. uuid = String.split(note.data["id"], "/") |> List.last()
  288. conn1 =
  289. conn
  290. |> put_req_header("accept", "application/activity+json")
  291. |> get("/objects/#{uuid}")
  292. assert json_response(conn1, :ok)
  293. assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
  294. Object.delete(note)
  295. conn2 =
  296. conn
  297. |> put_req_header("accept", "application/activity+json")
  298. |> get("/objects/#{uuid}")
  299. assert "Not found" == json_response(conn2, :not_found)
  300. end
  301. end
  302. describe "/activities/:uuid" do
  303. test "it doesn't return a local-only activity", %{conn: conn} do
  304. user = insert(:user)
  305. {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
  306. assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
  307. uuid = String.split(post.data["id"], "/") |> List.last()
  308. conn =
  309. conn
  310. |> put_req_header("accept", "application/json")
  311. |> get("/activities/#{uuid}")
  312. assert json_response(conn, 404)
  313. end
  314. test "returns local-only activities when authenticated", %{conn: conn} do
  315. user = insert(:user)
  316. {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
  317. assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
  318. uuid = String.split(post.data["id"], "/") |> List.last()
  319. assert response =
  320. conn
  321. |> assign(:user, user)
  322. |> put_req_header("accept", "application/activity+json")
  323. |> get("/activities/#{uuid}")
  324. assert json_response(response, 200) == ObjectView.render("object.json", %{object: post})
  325. end
  326. test "it returns a json representation of the activity", %{conn: conn} do
  327. activity = insert(:note_activity)
  328. uuid = String.split(activity.data["id"], "/") |> List.last()
  329. conn =
  330. conn
  331. |> put_req_header("accept", "application/activity+json")
  332. |> get("/activities/#{uuid}")
  333. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
  334. end
  335. test "it returns 404 for non-public activities", %{conn: conn} do
  336. activity = insert(:direct_note_activity)
  337. uuid = String.split(activity.data["id"], "/") |> List.last()
  338. conn =
  339. conn
  340. |> put_req_header("accept", "application/activity+json")
  341. |> get("/activities/#{uuid}")
  342. assert json_response(conn, 404)
  343. end
  344. test "returns visible non-public messages when authenticated", %{conn: conn} do
  345. note = insert(:direct_note_activity)
  346. uuid = String.split(note.data["id"], "/") |> List.last()
  347. user = User.get_by_ap_id(note.data["actor"])
  348. marisa = insert(:user)
  349. assert conn
  350. |> assign(:user, marisa)
  351. |> put_req_header("accept", "application/activity+json")
  352. |> get("/activities/#{uuid}")
  353. |> json_response(404)
  354. assert response =
  355. conn
  356. |> assign(:user, user)
  357. |> put_req_header("accept", "application/activity+json")
  358. |> get("/activities/#{uuid}")
  359. |> json_response(200)
  360. assert response == ObjectView.render("object.json", %{object: note})
  361. end
  362. test "it caches a response", %{conn: conn} do
  363. activity = insert(:note_activity)
  364. uuid = String.split(activity.data["id"], "/") |> List.last()
  365. conn1 =
  366. conn
  367. |> put_req_header("accept", "application/activity+json")
  368. |> get("/activities/#{uuid}")
  369. assert json_response(conn1, :ok)
  370. assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
  371. conn2 =
  372. conn
  373. |> put_req_header("accept", "application/activity+json")
  374. |> get("/activities/#{uuid}")
  375. assert json_response(conn1, :ok) == json_response(conn2, :ok)
  376. assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
  377. end
  378. test "cached purged after activity deletion", %{conn: conn} do
  379. user = insert(:user)
  380. {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
  381. uuid = String.split(activity.data["id"], "/") |> List.last()
  382. conn1 =
  383. conn
  384. |> put_req_header("accept", "application/activity+json")
  385. |> get("/activities/#{uuid}")
  386. assert json_response(conn1, :ok)
  387. assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
  388. Activity.delete_all_by_object_ap_id(activity.object.data["id"])
  389. conn2 =
  390. conn
  391. |> put_req_header("accept", "application/activity+json")
  392. |> get("/activities/#{uuid}")
  393. assert "Not found" == json_response(conn2, :not_found)
  394. end
  395. end
  396. describe "/inbox" do
  397. test "it inserts an incoming activity into the database", %{conn: conn} do
  398. data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
  399. conn =
  400. conn
  401. |> assign(:valid_signature, true)
  402. |> put_req_header("content-type", "application/activity+json")
  403. |> post("/inbox", data)
  404. assert "ok" == json_response(conn, 200)
  405. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  406. assert Activity.get_by_ap_id(data["id"])
  407. end
  408. @tag capture_log: true
  409. test "it inserts an incoming activity into the database" <>
  410. "even if we can't fetch the user but have it in our db",
  411. %{conn: conn} do
  412. user =
  413. insert(:user,
  414. ap_id: "https://mastodon.example.org/users/raymoo",
  415. ap_enabled: true,
  416. local: false,
  417. last_refreshed_at: nil
  418. )
  419. data =
  420. File.read!("test/fixtures/mastodon-post-activity.json")
  421. |> Jason.decode!()
  422. |> Map.put("actor", user.ap_id)
  423. |> put_in(["object", "attributedTo"], user.ap_id)
  424. conn =
  425. conn
  426. |> assign(:valid_signature, true)
  427. |> put_req_header("content-type", "application/activity+json")
  428. |> post("/inbox", data)
  429. assert "ok" == json_response(conn, 200)
  430. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  431. assert Activity.get_by_ap_id(data["id"])
  432. end
  433. test "it clears `unreachable` federation status of the sender", %{conn: conn} do
  434. data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
  435. sender_url = data["actor"]
  436. Instances.set_consistently_unreachable(sender_url)
  437. refute Instances.reachable?(sender_url)
  438. conn =
  439. conn
  440. |> assign(:valid_signature, true)
  441. |> put_req_header("content-type", "application/activity+json")
  442. |> post("/inbox", data)
  443. assert "ok" == json_response(conn, 200)
  444. assert Instances.reachable?(sender_url)
  445. end
  446. test "accept follow activity", %{conn: conn} do
  447. clear_config([:instance, :federating], true)
  448. relay = Relay.get_actor()
  449. assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
  450. followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
  451. relay = refresh_record(relay)
  452. accept =
  453. File.read!("test/fixtures/relay/accept-follow.json")
  454. |> String.replace("{{ap_id}}", relay.ap_id)
  455. |> String.replace("{{activity_id}}", activity.data["id"])
  456. assert "ok" ==
  457. conn
  458. |> assign(:valid_signature, true)
  459. |> put_req_header("content-type", "application/activity+json")
  460. |> post("/inbox", accept)
  461. |> json_response(200)
  462. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  463. assert Pleroma.FollowingRelationship.following?(
  464. relay,
  465. followed_relay
  466. )
  467. Mix.shell(Mix.Shell.Process)
  468. on_exit(fn ->
  469. Mix.shell(Mix.Shell.IO)
  470. end)
  471. :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
  472. assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
  473. end
  474. @tag capture_log: true
  475. test "without valid signature, " <>
  476. "it only accepts Create activities and requires enabled federation",
  477. %{conn: conn} do
  478. data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
  479. non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
  480. conn = put_req_header(conn, "content-type", "application/activity+json")
  481. clear_config([:instance, :federating], false)
  482. conn
  483. |> post("/inbox", data)
  484. |> json_response(403)
  485. conn
  486. |> post("/inbox", non_create_data)
  487. |> json_response(403)
  488. clear_config([:instance, :federating], true)
  489. ret_conn = post(conn, "/inbox", data)
  490. assert "ok" == json_response(ret_conn, 200)
  491. conn
  492. |> post("/inbox", non_create_data)
  493. |> json_response(400)
  494. end
  495. test "accepts Add/Remove activities", %{conn: conn} do
  496. object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
  497. status =
  498. File.read!("test/fixtures/statuses/note.json")
  499. |> String.replace("{{nickname}}", "lain")
  500. |> String.replace("{{object_id}}", object_id)
  501. object_url = "https://example.com/objects/#{object_id}"
  502. user =
  503. File.read!("test/fixtures/users_mock/user.json")
  504. |> String.replace("{{nickname}}", "lain")
  505. actor = "https://example.com/users/lain"
  506. Tesla.Mock.mock(fn
  507. %{
  508. method: :get,
  509. url: ^object_url
  510. } ->
  511. %Tesla.Env{
  512. status: 200,
  513. body: status,
  514. headers: [{"content-type", "application/activity+json"}]
  515. }
  516. %{
  517. method: :get,
  518. url: ^actor
  519. } ->
  520. %Tesla.Env{
  521. status: 200,
  522. body: user,
  523. headers: [{"content-type", "application/activity+json"}]
  524. }
  525. %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
  526. %Tesla.Env{
  527. status: 200,
  528. body:
  529. "test/fixtures/users_mock/masto_featured.json"
  530. |> File.read!()
  531. |> String.replace("{{domain}}", "example.com")
  532. |> String.replace("{{nickname}}", "lain"),
  533. headers: [{"content-type", "application/activity+json"}]
  534. }
  535. end)
  536. data = %{
  537. "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
  538. "actor" => actor,
  539. "object" => object_url,
  540. "target" => "https://example.com/users/lain/collections/featured",
  541. "type" => "Add",
  542. "to" => [Pleroma.Constants.as_public()]
  543. }
  544. assert "ok" ==
  545. conn
  546. |> assign(:valid_signature, true)
  547. |> put_req_header("content-type", "application/activity+json")
  548. |> post("/inbox", data)
  549. |> json_response(200)
  550. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  551. assert Activity.get_by_ap_id(data["id"])
  552. user = User.get_cached_by_ap_id(data["actor"])
  553. assert user.pinned_objects[data["object"]]
  554. data = %{
  555. "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
  556. "actor" => actor,
  557. "object" => object_url,
  558. "target" => "https://example.com/users/lain/collections/featured",
  559. "type" => "Remove",
  560. "to" => [Pleroma.Constants.as_public()]
  561. }
  562. assert "ok" ==
  563. conn
  564. |> assign(:valid_signature, true)
  565. |> put_req_header("content-type", "application/activity+json")
  566. |> post("/inbox", data)
  567. |> json_response(200)
  568. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  569. user = refresh_record(user)
  570. refute user.pinned_objects[data["object"]]
  571. end
  572. test "mastodon pin/unpin", %{conn: conn} do
  573. status_id = "105786274556060421"
  574. status =
  575. File.read!("test/fixtures/statuses/masto-note.json")
  576. |> String.replace("{{nickname}}", "lain")
  577. |> String.replace("{{status_id}}", status_id)
  578. status_url = "https://example.com/users/lain/statuses/#{status_id}"
  579. user =
  580. File.read!("test/fixtures/users_mock/user.json")
  581. |> String.replace("{{nickname}}", "lain")
  582. actor = "https://example.com/users/lain"
  583. Tesla.Mock.mock(fn
  584. %{
  585. method: :get,
  586. url: ^status_url
  587. } ->
  588. %Tesla.Env{
  589. status: 200,
  590. body: status,
  591. headers: [{"content-type", "application/activity+json"}]
  592. }
  593. %{
  594. method: :get,
  595. url: ^actor
  596. } ->
  597. %Tesla.Env{
  598. status: 200,
  599. body: user,
  600. headers: [{"content-type", "application/activity+json"}]
  601. }
  602. %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
  603. %Tesla.Env{
  604. status: 200,
  605. body:
  606. "test/fixtures/users_mock/masto_featured.json"
  607. |> File.read!()
  608. |> String.replace("{{domain}}", "example.com")
  609. |> String.replace("{{nickname}}", "lain"),
  610. headers: [{"content-type", "application/activity+json"}]
  611. }
  612. end)
  613. data = %{
  614. "@context" => "https://www.w3.org/ns/activitystreams",
  615. "actor" => actor,
  616. "object" => status_url,
  617. "target" => "https://example.com/users/lain/collections/featured",
  618. "type" => "Add"
  619. }
  620. assert "ok" ==
  621. conn
  622. |> assign(:valid_signature, true)
  623. |> put_req_header("content-type", "application/activity+json")
  624. |> post("/inbox", data)
  625. |> json_response(200)
  626. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  627. assert Activity.get_by_object_ap_id_with_object(data["object"])
  628. user = User.get_cached_by_ap_id(data["actor"])
  629. assert user.pinned_objects[data["object"]]
  630. data = %{
  631. "actor" => actor,
  632. "object" => status_url,
  633. "target" => "https://example.com/users/lain/collections/featured",
  634. "type" => "Remove"
  635. }
  636. assert "ok" ==
  637. conn
  638. |> assign(:valid_signature, true)
  639. |> put_req_header("content-type", "application/activity+json")
  640. |> post("/inbox", data)
  641. |> json_response(200)
  642. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  643. assert Activity.get_by_object_ap_id_with_object(data["object"])
  644. user = refresh_record(user)
  645. refute user.pinned_objects[data["object"]]
  646. end
  647. end
  648. describe "/users/:nickname/inbox" do
  649. setup do
  650. data =
  651. File.read!("test/fixtures/mastodon-post-activity.json")
  652. |> Jason.decode!()
  653. [data: data]
  654. end
  655. test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
  656. user = insert(:user)
  657. data =
  658. data
  659. |> Map.put("bcc", [user.ap_id])
  660. |> Kernel.put_in(["object", "bcc"], [user.ap_id])
  661. conn =
  662. conn
  663. |> assign(:valid_signature, true)
  664. |> put_req_header("content-type", "application/activity+json")
  665. |> post("/users/#{user.nickname}/inbox", data)
  666. assert "ok" == json_response(conn, 200)
  667. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  668. assert Activity.get_by_ap_id(data["id"])
  669. end
  670. test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
  671. user = insert(:user)
  672. data =
  673. data
  674. |> Map.put("to", user.ap_id)
  675. |> Map.put("cc", [])
  676. |> Kernel.put_in(["object", "to"], user.ap_id)
  677. |> Kernel.put_in(["object", "cc"], [])
  678. conn =
  679. conn
  680. |> assign(:valid_signature, true)
  681. |> put_req_header("content-type", "application/activity+json")
  682. |> post("/users/#{user.nickname}/inbox", data)
  683. assert "ok" == json_response(conn, 200)
  684. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  685. assert Activity.get_by_ap_id(data["id"])
  686. end
  687. test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
  688. user = insert(:user)
  689. data =
  690. data
  691. |> Map.put("to", [])
  692. |> Map.put("cc", user.ap_id)
  693. |> Kernel.put_in(["object", "to"], [])
  694. |> Kernel.put_in(["object", "cc"], user.ap_id)
  695. conn =
  696. conn
  697. |> assign(:valid_signature, true)
  698. |> put_req_header("content-type", "application/activity+json")
  699. |> post("/users/#{user.nickname}/inbox", data)
  700. assert "ok" == json_response(conn, 200)
  701. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  702. %Activity{} = activity = Activity.get_by_ap_id(data["id"])
  703. assert user.ap_id in activity.recipients
  704. end
  705. test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
  706. user = insert(:user)
  707. data =
  708. data
  709. |> Map.put("to", [])
  710. |> Map.put("cc", [])
  711. |> Map.put("bcc", user.ap_id)
  712. |> Kernel.put_in(["object", "to"], [])
  713. |> Kernel.put_in(["object", "cc"], [])
  714. |> Kernel.put_in(["object", "bcc"], user.ap_id)
  715. conn =
  716. conn
  717. |> assign(:valid_signature, true)
  718. |> put_req_header("content-type", "application/activity+json")
  719. |> post("/users/#{user.nickname}/inbox", data)
  720. assert "ok" == json_response(conn, 200)
  721. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  722. assert Activity.get_by_ap_id(data["id"])
  723. end
  724. test "it accepts announces with to as string instead of array", %{conn: conn} do
  725. user = insert(:user)
  726. {:ok, post} = CommonAPI.post(user, %{status: "hey"})
  727. announcer = insert(:user, local: false)
  728. data = %{
  729. "@context" => "https://www.w3.org/ns/activitystreams",
  730. "actor" => announcer.ap_id,
  731. "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
  732. "object" => post.data["object"],
  733. "to" => "https://www.w3.org/ns/activitystreams#Public",
  734. "cc" => [user.ap_id],
  735. "type" => "Announce"
  736. }
  737. conn =
  738. conn
  739. |> assign(:valid_signature, true)
  740. |> put_req_header("content-type", "application/activity+json")
  741. |> post("/users/#{user.nickname}/inbox", data)
  742. assert "ok" == json_response(conn, 200)
  743. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  744. %Activity{} = activity = Activity.get_by_ap_id(data["id"])
  745. assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
  746. end
  747. test "it accepts messages from actors that are followed by the user", %{
  748. conn: conn,
  749. data: data
  750. } do
  751. recipient = insert(:user)
  752. actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
  753. {:ok, recipient, actor} = User.follow(recipient, actor)
  754. object =
  755. data["object"]
  756. |> Map.put("attributedTo", actor.ap_id)
  757. data =
  758. data
  759. |> Map.put("actor", actor.ap_id)
  760. |> Map.put("object", object)
  761. conn =
  762. conn
  763. |> assign(:valid_signature, true)
  764. |> put_req_header("content-type", "application/activity+json")
  765. |> post("/users/#{recipient.nickname}/inbox", data)
  766. assert "ok" == json_response(conn, 200)
  767. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  768. assert Activity.get_by_ap_id(data["id"])
  769. end
  770. test "it rejects reads from other users", %{conn: conn} do
  771. user = insert(:user)
  772. other_user = insert(:user)
  773. conn =
  774. conn
  775. |> assign(:user, other_user)
  776. |> put_req_header("accept", "application/activity+json")
  777. |> get("/users/#{user.nickname}/inbox")
  778. assert json_response(conn, 403)
  779. end
  780. test "it returns a note activity in a collection", %{conn: conn} do
  781. note_activity = insert(:direct_note_activity)
  782. note_object = Object.normalize(note_activity, fetch: false)
  783. user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
  784. conn =
  785. conn
  786. |> assign(:user, user)
  787. |> put_req_header("accept", "application/activity+json")
  788. |> get("/users/#{user.nickname}/inbox?page=true")
  789. assert response(conn, 200) =~ note_object.data["content"]
  790. end
  791. test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
  792. user = insert(:user)
  793. data = Map.put(data, "bcc", [user.ap_id])
  794. sender_host = URI.parse(data["actor"]).host
  795. Instances.set_consistently_unreachable(sender_host)
  796. refute Instances.reachable?(sender_host)
  797. conn =
  798. conn
  799. |> assign(:valid_signature, true)
  800. |> put_req_header("content-type", "application/activity+json")
  801. |> post("/users/#{user.nickname}/inbox", data)
  802. assert "ok" == json_response(conn, 200)
  803. assert Instances.reachable?(sender_host)
  804. end
  805. @tag capture_log: true
  806. test "it removes all follower collections but actor's", %{conn: conn} do
  807. [actor, recipient] = insert_pair(:user)
  808. to = [
  809. recipient.ap_id,
  810. recipient.follower_address,
  811. "https://www.w3.org/ns/activitystreams#Public"
  812. ]
  813. cc = [recipient.follower_address, actor.follower_address]
  814. data = %{
  815. "@context" => ["https://www.w3.org/ns/activitystreams"],
  816. "type" => "Create",
  817. "id" => Utils.generate_activity_id(),
  818. "to" => to,
  819. "cc" => cc,
  820. "actor" => actor.ap_id,
  821. "object" => %{
  822. "type" => "Note",
  823. "to" => to,
  824. "cc" => cc,
  825. "content" => "It's a note",
  826. "attributedTo" => actor.ap_id,
  827. "id" => Utils.generate_object_id()
  828. }
  829. }
  830. conn
  831. |> assign(:valid_signature, true)
  832. |> put_req_header("content-type", "application/activity+json")
  833. |> post("/users/#{recipient.nickname}/inbox", data)
  834. |> json_response(200)
  835. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  836. assert activity = Activity.get_by_ap_id(data["id"])
  837. assert activity.id
  838. assert actor.follower_address in activity.recipients
  839. assert actor.follower_address in activity.data["cc"]
  840. refute recipient.follower_address in activity.recipients
  841. refute recipient.follower_address in activity.data["cc"]
  842. refute recipient.follower_address in activity.data["to"]
  843. end
  844. test "it requires authentication", %{conn: conn} do
  845. user = insert(:user)
  846. conn = put_req_header(conn, "accept", "application/activity+json")
  847. ret_conn = get(conn, "/users/#{user.nickname}/inbox")
  848. assert json_response(ret_conn, 403)
  849. ret_conn =
  850. conn
  851. |> assign(:user, user)
  852. |> get("/users/#{user.nickname}/inbox")
  853. assert json_response(ret_conn, 200)
  854. end
  855. @tag capture_log: true
  856. test "forwarded report", %{conn: conn} do
  857. admin = insert(:user, is_admin: true)
  858. actor = insert(:user, local: false)
  859. remote_domain = URI.parse(actor.ap_id).host
  860. reported_user = insert(:user)
  861. note = insert(:note_activity, user: reported_user)
  862. data = %{
  863. "@context" => [
  864. "https://www.w3.org/ns/activitystreams",
  865. "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
  866. %{
  867. "@language" => "und"
  868. }
  869. ],
  870. "actor" => actor.ap_id,
  871. "cc" => [
  872. reported_user.ap_id
  873. ],
  874. "content" => "test",
  875. "context" => "context",
  876. "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
  877. "nickname" => reported_user.nickname,
  878. "object" => [
  879. reported_user.ap_id,
  880. %{
  881. "actor" => %{
  882. "actor_type" => "Person",
  883. "approval_pending" => false,
  884. "avatar" => "",
  885. "confirmation_pending" => false,
  886. "deactivated" => false,
  887. "display_name" => "test user",
  888. "id" => reported_user.id,
  889. "local" => false,
  890. "nickname" => reported_user.nickname,
  891. "registration_reason" => nil,
  892. "roles" => %{
  893. "admin" => false,
  894. "moderator" => false
  895. },
  896. "tags" => [],
  897. "url" => reported_user.ap_id
  898. },
  899. "content" => "",
  900. "id" => note.data["id"],
  901. "published" => note.data["published"],
  902. "type" => "Note"
  903. }
  904. ],
  905. "published" => note.data["published"],
  906. "state" => "open",
  907. "to" => [],
  908. "type" => "Flag"
  909. }
  910. conn
  911. |> assign(:valid_signature, true)
  912. |> put_req_header("content-type", "application/activity+json")
  913. |> post("/users/#{reported_user.nickname}/inbox", data)
  914. |> json_response(200)
  915. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  916. assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
  917. ObanHelpers.perform_all()
  918. Swoosh.TestAssertions.assert_email_sent(
  919. to: {admin.name, admin.email},
  920. html_body: ~r/Reported Account:/i
  921. )
  922. end
  923. @tag capture_log: true
  924. test "forwarded report from mastodon", %{conn: conn} do
  925. admin = insert(:user, is_admin: true)
  926. actor = insert(:user, local: false)
  927. remote_domain = URI.parse(actor.ap_id).host
  928. remote_actor = "https://#{remote_domain}/actor"
  929. [reported_user, another] = insert_list(2, :user)
  930. note = insert(:note_activity, user: reported_user)
  931. Pleroma.Web.CommonAPI.favorite(another, note.id)
  932. mock_json_body =
  933. "test/fixtures/mastodon/application_actor.json"
  934. |> File.read!()
  935. |> String.replace("{{DOMAIN}}", remote_domain)
  936. Tesla.Mock.mock(fn %{url: ^remote_actor} ->
  937. %Tesla.Env{
  938. status: 200,
  939. body: mock_json_body,
  940. headers: [{"content-type", "application/activity+json"}]
  941. }
  942. end)
  943. data = %{
  944. "@context" => "https://www.w3.org/ns/activitystreams",
  945. "actor" => remote_actor,
  946. "content" => "test report",
  947. "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
  948. "object" => [
  949. reported_user.ap_id,
  950. note.data["object"]
  951. ],
  952. "type" => "Flag"
  953. }
  954. conn
  955. |> assign(:valid_signature, true)
  956. |> put_req_header("content-type", "application/activity+json")
  957. |> post("/users/#{reported_user.nickname}/inbox", data)
  958. |> json_response(200)
  959. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  960. flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
  961. reported_user_ap_id = reported_user.ap_id
  962. [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
  963. Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
  964. ObanHelpers.perform_all()
  965. Swoosh.TestAssertions.assert_email_sent(
  966. to: {admin.name, admin.email},
  967. html_body: ~r/#{note.data["object"]}/i
  968. )
  969. end
  970. end
  971. describe "GET /users/:nickname/outbox" do
  972. test "it paginates correctly", %{conn: conn} do
  973. user = insert(:user)
  974. conn = assign(conn, :user, user)
  975. outbox_endpoint = user.ap_id <> "/outbox"
  976. _posts =
  977. for i <- 0..25 do
  978. {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
  979. activity
  980. end
  981. result =
  982. conn
  983. |> put_req_header("accept", "application/activity+json")
  984. |> get(outbox_endpoint <> "?page=true")
  985. |> json_response(200)
  986. result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
  987. assert length(result["orderedItems"]) == 20
  988. assert length(result_ids) == 20
  989. assert result["next"]
  990. assert String.starts_with?(result["next"], outbox_endpoint)
  991. result_next =
  992. conn
  993. |> put_req_header("accept", "application/activity+json")
  994. |> get(result["next"])
  995. |> json_response(200)
  996. result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
  997. assert length(result_next["orderedItems"]) == 6
  998. assert length(result_next_ids) == 6
  999. refute Enum.find(result_next_ids, fn x -> x in result_ids end)
  1000. refute Enum.find(result_ids, fn x -> x in result_next_ids end)
  1001. assert String.starts_with?(result["id"], outbox_endpoint)
  1002. result_next_again =
  1003. conn
  1004. |> put_req_header("accept", "application/activity+json")
  1005. |> get(result_next["id"])
  1006. |> json_response(200)
  1007. assert result_next == result_next_again
  1008. end
  1009. test "it returns 200 even if there're no activities", %{conn: conn} do
  1010. user = insert(:user)
  1011. outbox_endpoint = user.ap_id <> "/outbox"
  1012. conn =
  1013. conn
  1014. |> assign(:user, user)
  1015. |> put_req_header("accept", "application/activity+json")
  1016. |> get(outbox_endpoint)
  1017. result = json_response(conn, 200)
  1018. assert outbox_endpoint == result["id"]
  1019. end
  1020. test "it returns a note activity in a collection", %{conn: conn} do
  1021. note_activity = insert(:note_activity)
  1022. note_object = Object.normalize(note_activity, fetch: false)
  1023. user = User.get_cached_by_ap_id(note_activity.data["actor"])
  1024. conn =
  1025. conn
  1026. |> assign(:user, user)
  1027. |> put_req_header("accept", "application/activity+json")
  1028. |> get("/users/#{user.nickname}/outbox?page=true")
  1029. assert response(conn, 200) =~ note_object.data["content"]
  1030. end
  1031. test "it returns an announce activity in a collection", %{conn: conn} do
  1032. announce_activity = insert(:announce_activity)
  1033. user = User.get_cached_by_ap_id(announce_activity.data["actor"])
  1034. conn =
  1035. conn
  1036. |> assign(:user, user)
  1037. |> put_req_header("accept", "application/activity+json")
  1038. |> get("/users/#{user.nickname}/outbox?page=true")
  1039. assert response(conn, 200) =~ announce_activity.data["object"]
  1040. end
  1041. test "It returns poll Answers when authenticated", %{conn: conn} do
  1042. poller = insert(:user)
  1043. voter = insert(:user)
  1044. {:ok, activity} =
  1045. CommonAPI.post(poller, %{
  1046. status: "suya...",
  1047. poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
  1048. })
  1049. assert question = Object.normalize(activity, fetch: false)
  1050. {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
  1051. assert outbox_get =
  1052. conn
  1053. |> assign(:user, voter)
  1054. |> put_req_header("accept", "application/activity+json")
  1055. |> get(voter.ap_id <> "/outbox?page=true")
  1056. |> json_response(200)
  1057. assert [answer_outbox] = outbox_get["orderedItems"]
  1058. assert answer_outbox["id"] == activity.data["id"]
  1059. end
  1060. end
  1061. describe "POST /users/:nickname/outbox (C2S)" do
  1062. setup do: clear_config([:instance, :limit])
  1063. setup do
  1064. [
  1065. activity: %{
  1066. "@context" => "https://www.w3.org/ns/activitystreams",
  1067. "type" => "Create",
  1068. "object" => %{
  1069. "type" => "Note",
  1070. "content" => "AP C2S test",
  1071. "to" => "https://www.w3.org/ns/activitystreams#Public",
  1072. "cc" => []
  1073. }
  1074. }
  1075. ]
  1076. end
  1077. test "it rejects posts from other users / unauthenticated users", %{
  1078. conn: conn,
  1079. activity: activity
  1080. } do
  1081. user = insert(:user)
  1082. other_user = insert(:user)
  1083. conn = put_req_header(conn, "content-type", "application/activity+json")
  1084. conn
  1085. |> post("/users/#{user.nickname}/outbox", activity)
  1086. |> json_response(403)
  1087. conn
  1088. |> assign(:user, other_user)
  1089. |> post("/users/#{user.nickname}/outbox", activity)
  1090. |> json_response(403)
  1091. end
  1092. test "it inserts an incoming create activity into the database", %{
  1093. conn: conn,
  1094. activity: activity
  1095. } do
  1096. user = insert(:user)
  1097. result =
  1098. conn
  1099. |> assign(:user, user)
  1100. |> put_req_header("content-type", "application/activity+json")
  1101. |> post("/users/#{user.nickname}/outbox", activity)
  1102. |> json_response(201)
  1103. assert Activity.get_by_ap_id(result["id"])
  1104. assert result["object"]
  1105. assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
  1106. assert object["content"] == activity["object"]["content"]
  1107. end
  1108. test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
  1109. user = insert(:user)
  1110. activity =
  1111. activity
  1112. |> put_in(["object", "type"], "Benis")
  1113. _result =
  1114. conn
  1115. |> assign(:user, user)
  1116. |> put_req_header("content-type", "application/activity+json")
  1117. |> post("/users/#{user.nickname}/outbox", activity)
  1118. |> json_response(400)
  1119. end
  1120. test "it inserts an incoming sensitive activity into the database", %{
  1121. conn: conn,
  1122. activity: activity
  1123. } do
  1124. user = insert(:user)
  1125. conn = assign(conn, :user, user)
  1126. object = Map.put(activity["object"], "sensitive", true)
  1127. activity = Map.put(activity, "object", object)
  1128. response =
  1129. conn
  1130. |> put_req_header("content-type", "application/activity+json")
  1131. |> post("/users/#{user.nickname}/outbox", activity)
  1132. |> json_response(201)
  1133. assert Activity.get_by_ap_id(response["id"])
  1134. assert response["object"]
  1135. assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
  1136. assert response_object["sensitive"] == true
  1137. assert response_object["content"] == activity["object"]["content"]
  1138. representation =
  1139. conn
  1140. |> put_req_header("accept", "application/activity+json")
  1141. |> get(response["id"])
  1142. |> json_response(200)
  1143. assert representation["object"]["sensitive"] == true
  1144. end
  1145. test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
  1146. user = insert(:user)
  1147. activity = Map.put(activity, "type", "BadType")
  1148. conn =
  1149. conn
  1150. |> assign(:user, user)
  1151. |> put_req_header("content-type", "application/activity+json")
  1152. |> post("/users/#{user.nickname}/outbox", activity)
  1153. assert json_response(conn, 400)
  1154. end
  1155. test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
  1156. note_activity = insert(:note_activity)
  1157. note_object = Object.normalize(note_activity, fetch: false)
  1158. user = User.get_cached_by_ap_id(note_activity.data["actor"])
  1159. data = %{
  1160. "type" => "Delete",
  1161. "object" => %{
  1162. "id" => note_object.data["id"]
  1163. }
  1164. }
  1165. result =
  1166. conn
  1167. |> assign(:user, user)
  1168. |> put_req_header("content-type", "application/activity+json")
  1169. |> post("/users/#{user.nickname}/outbox", data)
  1170. |> json_response(201)
  1171. assert Activity.get_by_ap_id(result["id"])
  1172. assert object = Object.get_by_ap_id(note_object.data["id"])
  1173. assert object.data["type"] == "Tombstone"
  1174. end
  1175. test "it rejects delete activity of object from other actor", %{conn: conn} do
  1176. note_activity = insert(:note_activity)
  1177. note_object = Object.normalize(note_activity, fetch: false)
  1178. user = insert(:user)
  1179. data = %{
  1180. type: "Delete",
  1181. object: %{
  1182. id: note_object.data["id"]
  1183. }
  1184. }
  1185. conn =
  1186. conn
  1187. |> assign(:user, user)
  1188. |> put_req_header("content-type", "application/activity+json")
  1189. |> post("/users/#{user.nickname}/outbox", data)
  1190. assert json_response(conn, 403)
  1191. end
  1192. test "it increases like count when receiving a like action", %{conn: conn} do
  1193. note_activity = insert(:note_activity)
  1194. note_object = Object.normalize(note_activity, fetch: false)
  1195. user = User.get_cached_by_ap_id(note_activity.data["actor"])
  1196. data = %{
  1197. type: "Like",
  1198. object: %{
  1199. id: note_object.data["id"]
  1200. }
  1201. }
  1202. conn =
  1203. conn
  1204. |> assign(:user, user)
  1205. |> put_req_header("content-type", "application/activity+json")
  1206. |> post("/users/#{user.nickname}/outbox", data)
  1207. result = json_response(conn, 201)
  1208. assert Activity.get_by_ap_id(result["id"])
  1209. assert object = Object.get_by_ap_id(note_object.data["id"])
  1210. assert object.data["like_count"] == 1
  1211. end
  1212. test "it doesn't spreads faulty attributedTo or actor fields", %{
  1213. conn: conn,
  1214. activity: activity
  1215. } do
  1216. reimu = insert(:user, nickname: "reimu")
  1217. cirno = insert(:user, nickname: "cirno")
  1218. assert reimu.ap_id
  1219. assert cirno.ap_id
  1220. activity =
  1221. activity
  1222. |> put_in(["object", "actor"], reimu.ap_id)
  1223. |> put_in(["object", "attributedTo"], reimu.ap_id)
  1224. |> put_in(["actor"], reimu.ap_id)
  1225. |> put_in(["attributedTo"], reimu.ap_id)
  1226. _reimu_outbox =
  1227. conn
  1228. |> assign(:user, cirno)
  1229. |> put_req_header("content-type", "application/activity+json")
  1230. |> post("/users/#{reimu.nickname}/outbox", activity)
  1231. |> json_response(403)
  1232. cirno_outbox =
  1233. conn
  1234. |> assign(:user, cirno)
  1235. |> put_req_header("content-type", "application/activity+json")
  1236. |> post("/users/#{cirno.nickname}/outbox", activity)
  1237. |> json_response(201)
  1238. assert cirno_outbox["attributedTo"] == nil
  1239. assert cirno_outbox["actor"] == cirno.ap_id
  1240. assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
  1241. assert cirno_object.data["actor"] == cirno.ap_id
  1242. assert cirno_object.data["attributedTo"] == cirno.ap_id
  1243. end
  1244. test "Character limitation", %{conn: conn, activity: activity} do
  1245. clear_config([:instance, :limit], 5)
  1246. user = insert(:user)
  1247. result =
  1248. conn
  1249. |> assign(:user, user)
  1250. |> put_req_header("content-type", "application/activity+json")
  1251. |> post("/users/#{user.nickname}/outbox", activity)
  1252. |> json_response(400)
  1253. assert result == "Character limit (5 characters) exceeded, contains 11 characters"
  1254. end
  1255. end
  1256. describe "/relay/followers" do
  1257. test "it returns relay followers", %{conn: conn} do
  1258. relay_actor = Relay.get_actor()
  1259. user = insert(:user)
  1260. User.follow(user, relay_actor)
  1261. result =
  1262. conn
  1263. |> get("/relay/followers")
  1264. |> json_response(200)
  1265. assert result["first"]["orderedItems"] == [user.ap_id]
  1266. end
  1267. test "on non-federating instance, it returns 404", %{conn: conn} do
  1268. clear_config([:instance, :federating], false)
  1269. user = insert(:user)
  1270. conn
  1271. |> assign(:user, user)
  1272. |> get("/relay/followers")
  1273. |> json_response(404)
  1274. end
  1275. end
  1276. describe "/relay/following" do
  1277. test "it returns relay following", %{conn: conn} do
  1278. result =
  1279. conn
  1280. |> get("/relay/following")
  1281. |> json_response(200)
  1282. assert result["first"]["orderedItems"] == []
  1283. end
  1284. test "on non-federating instance, it returns 404", %{conn: conn} do
  1285. clear_config([:instance, :federating], false)
  1286. user = insert(:user)
  1287. conn
  1288. |> assign(:user, user)
  1289. |> get("/relay/following")
  1290. |> json_response(404)
  1291. end
  1292. end
  1293. describe "/users/:nickname/followers" do
  1294. test "it returns the followers in a collection", %{conn: conn} do
  1295. user = insert(:user)
  1296. user_two = insert(:user)
  1297. User.follow(user, user_two)
  1298. result =
  1299. conn
  1300. |> assign(:user, user_two)
  1301. |> get("/users/#{user_two.nickname}/followers")
  1302. |> json_response(200)
  1303. assert result["first"]["orderedItems"] == [user.ap_id]
  1304. end
  1305. test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
  1306. user = insert(:user)
  1307. user_two = insert(:user, hide_followers: true)
  1308. User.follow(user, user_two)
  1309. result =
  1310. conn
  1311. |> assign(:user, user)
  1312. |> get("/users/#{user_two.nickname}/followers")
  1313. |> json_response(200)
  1314. assert is_binary(result["first"])
  1315. end
  1316. test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
  1317. %{conn: conn} do
  1318. user = insert(:user)
  1319. other_user = insert(:user, hide_followers: true)
  1320. result =
  1321. conn
  1322. |> assign(:user, user)
  1323. |> get("/users/#{other_user.nickname}/followers?page=1")
  1324. assert result.status == 403
  1325. assert result.resp_body == ""
  1326. end
  1327. test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
  1328. %{conn: conn} do
  1329. user = insert(:user, hide_followers: true)
  1330. other_user = insert(:user)
  1331. {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
  1332. result =
  1333. conn
  1334. |> assign(:user, user)
  1335. |> get("/users/#{user.nickname}/followers?page=1")
  1336. |> json_response(200)
  1337. assert result["totalItems"] == 1
  1338. assert result["orderedItems"] == [other_user.ap_id]
  1339. end
  1340. test "it works for more than 10 users", %{conn: conn} do
  1341. user = insert(:user)
  1342. Enum.each(1..15, fn _ ->
  1343. other_user = insert(:user)
  1344. User.follow(other_user, user)
  1345. end)
  1346. result =
  1347. conn
  1348. |> assign(:user, user)
  1349. |> get("/users/#{user.nickname}/followers")
  1350. |> json_response(200)
  1351. assert length(result["first"]["orderedItems"]) == 10
  1352. assert result["first"]["totalItems"] == 15
  1353. assert result["totalItems"] == 15
  1354. result =
  1355. conn
  1356. |> assign(:user, user)
  1357. |> get("/users/#{user.nickname}/followers?page=2")
  1358. |> json_response(200)
  1359. assert length(result["orderedItems"]) == 5
  1360. assert result["totalItems"] == 15
  1361. end
  1362. test "does not require authentication", %{conn: conn} do
  1363. user = insert(:user)
  1364. conn
  1365. |> get("/users/#{user.nickname}/followers")
  1366. |> json_response(200)
  1367. end
  1368. end
  1369. describe "/users/:nickname/following" do
  1370. test "it returns the following in a collection", %{conn: conn} do
  1371. user = insert(:user)
  1372. user_two = insert(:user)
  1373. User.follow(user, user_two)
  1374. result =
  1375. conn
  1376. |> assign(:user, user)
  1377. |> get("/users/#{user.nickname}/following")
  1378. |> json_response(200)
  1379. assert result["first"]["orderedItems"] == [user_two.ap_id]
  1380. end
  1381. test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
  1382. user = insert(:user)
  1383. user_two = insert(:user, hide_follows: true)
  1384. User.follow(user, user_two)
  1385. result =
  1386. conn
  1387. |> assign(:user, user)
  1388. |> get("/users/#{user_two.nickname}/following")
  1389. |> json_response(200)
  1390. assert is_binary(result["first"])
  1391. end
  1392. test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
  1393. %{conn: conn} do
  1394. user = insert(:user)
  1395. user_two = insert(:user, hide_follows: true)
  1396. result =
  1397. conn
  1398. |> assign(:user, user)
  1399. |> get("/users/#{user_two.nickname}/following?page=1")
  1400. assert result.status == 403
  1401. assert result.resp_body == ""
  1402. end
  1403. test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
  1404. %{conn: conn} do
  1405. user = insert(:user, hide_follows: true)
  1406. other_user = insert(:user)
  1407. {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
  1408. result =
  1409. conn
  1410. |> assign(:user, user)
  1411. |> get("/users/#{user.nickname}/following?page=1")
  1412. |> json_response(200)
  1413. assert result["totalItems"] == 1
  1414. assert result["orderedItems"] == [other_user.ap_id]
  1415. end
  1416. test "it works for more than 10 users", %{conn: conn} do
  1417. user = insert(:user)
  1418. Enum.each(1..15, fn _ ->
  1419. user = User.get_cached_by_id(user.id)
  1420. other_user = insert(:user)
  1421. User.follow(user, other_user)
  1422. end)
  1423. result =
  1424. conn
  1425. |> assign(:user, user)
  1426. |> get("/users/#{user.nickname}/following")
  1427. |> json_response(200)
  1428. assert length(result["first"]["orderedItems"]) == 10
  1429. assert result["first"]["totalItems"] == 15
  1430. assert result["totalItems"] == 15
  1431. result =
  1432. conn
  1433. |> assign(:user, user)
  1434. |> get("/users/#{user.nickname}/following?page=2")
  1435. |> json_response(200)
  1436. assert length(result["orderedItems"]) == 5
  1437. assert result["totalItems"] == 15
  1438. end
  1439. test "does not require authentication", %{conn: conn} do
  1440. user = insert(:user)
  1441. conn
  1442. |> get("/users/#{user.nickname}/following")
  1443. |> json_response(200)
  1444. end
  1445. end
  1446. describe "delivery tracking" do
  1447. test "it tracks a signed object fetch", %{conn: conn} do
  1448. user = insert(:user, local: false)
  1449. activity = insert(:note_activity)
  1450. object = Object.normalize(activity, fetch: false)
  1451. object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
  1452. conn
  1453. |> put_req_header("accept", "application/activity+json")
  1454. |> assign(:user, user)
  1455. |> get(object_path)
  1456. |> json_response(200)
  1457. assert Delivery.get(object.id, user.id)
  1458. end
  1459. test "it tracks a signed activity fetch", %{conn: conn} do
  1460. user = insert(:user, local: false)
  1461. activity = insert(:note_activity)
  1462. object = Object.normalize(activity, fetch: false)
  1463. activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
  1464. conn
  1465. |> put_req_header("accept", "application/activity+json")
  1466. |> assign(:user, user)
  1467. |> get(activity_path)
  1468. |> json_response(200)
  1469. assert Delivery.get(object.id, user.id)
  1470. end
  1471. test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
  1472. user = insert(:user, local: false)
  1473. other_user = insert(:user, local: false)
  1474. activity = insert(:note_activity)
  1475. object = Object.normalize(activity, fetch: false)
  1476. object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
  1477. conn
  1478. |> put_req_header("accept", "application/activity+json")
  1479. |> assign(:user, user)
  1480. |> get(object_path)
  1481. |> json_response(200)
  1482. build_conn()
  1483. |> put_req_header("accept", "application/activity+json")
  1484. |> assign(:user, other_user)
  1485. |> get(object_path)
  1486. |> json_response(200)
  1487. assert Delivery.get(object.id, user.id)
  1488. assert Delivery.get(object.id, other_user.id)
  1489. end
  1490. test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
  1491. user = insert(:user, local: false)
  1492. other_user = insert(:user, local: false)
  1493. activity = insert(:note_activity)
  1494. object = Object.normalize(activity, fetch: false)
  1495. activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
  1496. conn
  1497. |> put_req_header("accept", "application/activity+json")
  1498. |> assign(:user, user)
  1499. |> get(activity_path)
  1500. |> json_response(200)
  1501. build_conn()
  1502. |> put_req_header("accept", "application/activity+json")
  1503. |> assign(:user, other_user)
  1504. |> get(activity_path)
  1505. |> json_response(200)
  1506. assert Delivery.get(object.id, user.id)
  1507. assert Delivery.get(object.id, other_user.id)
  1508. end
  1509. end
  1510. describe "Additional ActivityPub C2S endpoints" do
  1511. test "GET /api/ap/whoami", %{conn: conn} do
  1512. user = insert(:user)
  1513. conn =
  1514. conn
  1515. |> assign(:user, user)
  1516. |> get("/api/ap/whoami")
  1517. user = User.get_cached_by_id(user.id)
  1518. assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
  1519. conn
  1520. |> get("/api/ap/whoami")
  1521. |> json_response(403)
  1522. end
  1523. setup do: clear_config([:media_proxy])
  1524. setup do: clear_config([Pleroma.Upload])
  1525. test "POST /api/ap/upload_media", %{conn: conn} do
  1526. user = insert(:user)
  1527. desc = "Description of the image"
  1528. image = %Plug.Upload{
  1529. content_type: "image/jpeg",
  1530. path: Path.absname("test/fixtures/image.jpg"),
  1531. filename: "an_image.jpg"
  1532. }
  1533. object =
  1534. conn
  1535. |> assign(:user, user)
  1536. |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
  1537. |> json_response(:created)
  1538. assert object["name"] == desc
  1539. assert object["type"] == "Document"
  1540. assert object["actor"] == user.ap_id
  1541. assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
  1542. assert is_binary(object_href)
  1543. assert object_mediatype == "image/jpeg"
  1544. assert String.ends_with?(object_href, ".jpg")
  1545. activity_request = %{
  1546. "@context" => "https://www.w3.org/ns/activitystreams",
  1547. "type" => "Create",
  1548. "object" => %{
  1549. "type" => "Note",
  1550. "content" => "AP C2S test, attachment",
  1551. "attachment" => [object],
  1552. "to" => "https://www.w3.org/ns/activitystreams#Public",
  1553. "cc" => []
  1554. }
  1555. }
  1556. activity_response =
  1557. conn
  1558. |> assign(:user, user)
  1559. |> post("/users/#{user.nickname}/outbox", activity_request)
  1560. |> json_response(:created)
  1561. assert activity_response["id"]
  1562. assert activity_response["object"]
  1563. assert activity_response["actor"] == user.ap_id
  1564. assert %Object{data: %{"attachment" => [attachment]}} =
  1565. Object.normalize(activity_response["object"], fetch: false)
  1566. assert attachment["type"] == "Document"
  1567. assert attachment["name"] == desc
  1568. assert [
  1569. %{
  1570. "href" => ^object_href,
  1571. "type" => "Link",
  1572. "mediaType" => ^object_mediatype
  1573. }
  1574. ] = attachment["url"]
  1575. # Fails if unauthenticated
  1576. conn
  1577. |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
  1578. |> json_response(403)
  1579. end
  1580. end
  1581. test "pinned collection", %{conn: conn} do
  1582. clear_config([:instance, :max_pinned_statuses], 2)
  1583. user = insert(:user)
  1584. objects = insert_list(2, :note, user: user)
  1585. Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
  1586. {:ok, updated} = User.add_pinned_object_id(user, object_id)
  1587. updated
  1588. end)
  1589. %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
  1590. refresh_record(user)
  1591. %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
  1592. conn
  1593. |> get("/users/#{nickname}/collections/featured")
  1594. |> json_response(200)
  1595. object_ids = Enum.map(items, & &1["id"])
  1596. assert Enum.all?(pinned_objects, fn {obj_id, _} ->
  1597. obj_id in object_ids
  1598. end)
  1599. end
  1600. end