logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

activity_pub_controller_test.exs (64920B)


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