logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma

activity_pub_controller_test.exs (47449B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2020 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.Config
  9. alias Pleroma.Delivery
  10. alias Pleroma.Instances
  11. alias Pleroma.Object
  12. alias Pleroma.Tests.ObanHelpers
  13. alias Pleroma.User
  14. alias Pleroma.Web.ActivityPub.ActivityPub
  15. alias Pleroma.Web.ActivityPub.ObjectView
  16. alias Pleroma.Web.ActivityPub.Relay
  17. alias Pleroma.Web.ActivityPub.UserView
  18. alias Pleroma.Web.ActivityPub.Utils
  19. alias Pleroma.Web.CommonAPI
  20. alias Pleroma.Web.Endpoint
  21. alias Pleroma.Workers.ReceiverWorker
  22. import Pleroma.Factory
  23. require Pleroma.Constants
  24. setup_all do
  25. Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
  26. :ok
  27. end
  28. setup do: clear_config([:instance, :federating], true)
  29. describe "/relay" do
  30. setup do: clear_config([:instance, :allow_relay])
  31. test "with the relay active, it returns the relay user", %{conn: conn} do
  32. res =
  33. conn
  34. |> get(activity_pub_path(conn, :relay))
  35. |> json_response(200)
  36. assert res["id"] =~ "/relay"
  37. end
  38. test "with the relay disabled, it returns 404", %{conn: conn} do
  39. Config.put([:instance, :allow_relay], false)
  40. conn
  41. |> get(activity_pub_path(conn, :relay))
  42. |> json_response(404)
  43. end
  44. test "on non-federating instance, it returns 404", %{conn: conn} do
  45. Config.put([:instance, :federating], false)
  46. user = insert(:user)
  47. conn
  48. |> assign(:user, user)
  49. |> get(activity_pub_path(conn, :relay))
  50. |> json_response(404)
  51. end
  52. end
  53. describe "/internal/fetch" do
  54. test "it returns the internal fetch user", %{conn: conn} do
  55. res =
  56. conn
  57. |> get(activity_pub_path(conn, :internal_fetch))
  58. |> json_response(200)
  59. assert res["id"] =~ "/fetch"
  60. end
  61. test "on non-federating instance, it returns 404", %{conn: conn} do
  62. Config.put([:instance, :federating], false)
  63. user = insert(:user)
  64. conn
  65. |> assign(:user, user)
  66. |> get(activity_pub_path(conn, :internal_fetch))
  67. |> json_response(404)
  68. end
  69. end
  70. describe "/users/:nickname" do
  71. test "it returns a json representation of the user with accept application/json", %{
  72. conn: conn
  73. } do
  74. user = insert(:user)
  75. conn =
  76. conn
  77. |> put_req_header("accept", "application/json")
  78. |> get("/users/#{user.nickname}")
  79. user = User.get_cached_by_id(user.id)
  80. assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
  81. end
  82. test "it returns a json representation of the user with accept application/activity+json", %{
  83. conn: conn
  84. } do
  85. user = insert(:user)
  86. conn =
  87. conn
  88. |> put_req_header("accept", "application/activity+json")
  89. |> get("/users/#{user.nickname}")
  90. user = User.get_cached_by_id(user.id)
  91. assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
  92. end
  93. test "it returns a json representation of the user with accept application/ld+json", %{
  94. conn: conn
  95. } do
  96. user = insert(:user)
  97. conn =
  98. conn
  99. |> put_req_header(
  100. "accept",
  101. "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
  102. )
  103. |> get("/users/#{user.nickname}")
  104. user = User.get_cached_by_id(user.id)
  105. assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
  106. end
  107. test "it returns 404 for remote users", %{
  108. conn: conn
  109. } do
  110. user = insert(:user, local: false, nickname: "remoteuser@example.com")
  111. conn =
  112. conn
  113. |> put_req_header("accept", "application/json")
  114. |> get("/users/#{user.nickname}.json")
  115. assert json_response(conn, 404)
  116. end
  117. test "it returns error when user is not found", %{conn: conn} do
  118. response =
  119. conn
  120. |> put_req_header("accept", "application/json")
  121. |> get("/users/jimm")
  122. |> json_response(404)
  123. assert response == "Not found"
  124. end
  125. test "it requires authentication if instance is NOT federating", %{
  126. conn: conn
  127. } do
  128. user = insert(:user)
  129. conn =
  130. put_req_header(
  131. conn,
  132. "accept",
  133. "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
  134. )
  135. ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user)
  136. end
  137. end
  138. describe "mastodon compatibility routes" do
  139. test "it returns a json representation of the object with accept application/json", %{
  140. conn: conn
  141. } do
  142. {:ok, object} =
  143. %{
  144. "type" => "Note",
  145. "content" => "hey",
  146. "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
  147. "actor" => Endpoint.url() <> "/users/raymoo",
  148. "to" => [Pleroma.Constants.as_public()]
  149. }
  150. |> Object.create()
  151. conn =
  152. conn
  153. |> put_req_header("accept", "application/json")
  154. |> get("/users/raymoo/statuses/999999999")
  155. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
  156. end
  157. test "it returns a json representation of the activity with accept application/json", %{
  158. conn: conn
  159. } do
  160. {:ok, object} =
  161. %{
  162. "type" => "Note",
  163. "content" => "hey",
  164. "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
  165. "actor" => Endpoint.url() <> "/users/raymoo",
  166. "to" => [Pleroma.Constants.as_public()]
  167. }
  168. |> Object.create()
  169. {:ok, activity, _} =
  170. %{
  171. "id" => object.data["id"] <> "/activity",
  172. "type" => "Create",
  173. "object" => object.data["id"],
  174. "actor" => object.data["actor"],
  175. "to" => object.data["to"]
  176. }
  177. |> ActivityPub.persist(local: true)
  178. conn =
  179. conn
  180. |> put_req_header("accept", "application/json")
  181. |> get("/users/raymoo/statuses/999999999/activity")
  182. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
  183. end
  184. end
  185. describe "/objects/:uuid" do
  186. test "it returns a json representation of the object with accept application/json", %{
  187. conn: conn
  188. } do
  189. note = insert(:note)
  190. uuid = String.split(note.data["id"], "/") |> List.last()
  191. conn =
  192. conn
  193. |> put_req_header("accept", "application/json")
  194. |> get("/objects/#{uuid}")
  195. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
  196. end
  197. test "it returns a json representation of the object with accept application/activity+json",
  198. %{conn: conn} do
  199. note = insert(:note)
  200. uuid = String.split(note.data["id"], "/") |> List.last()
  201. conn =
  202. conn
  203. |> put_req_header("accept", "application/activity+json")
  204. |> get("/objects/#{uuid}")
  205. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
  206. end
  207. test "it returns a json representation of the object with accept application/ld+json", %{
  208. conn: conn
  209. } do
  210. note = insert(:note)
  211. uuid = String.split(note.data["id"], "/") |> List.last()
  212. conn =
  213. conn
  214. |> put_req_header(
  215. "accept",
  216. "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
  217. )
  218. |> get("/objects/#{uuid}")
  219. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
  220. end
  221. test "it returns 404 for non-public messages", %{conn: conn} do
  222. note = insert(:direct_note)
  223. uuid = String.split(note.data["id"], "/") |> List.last()
  224. conn =
  225. conn
  226. |> put_req_header("accept", "application/activity+json")
  227. |> get("/objects/#{uuid}")
  228. assert json_response(conn, 404)
  229. end
  230. test "it returns 404 for tombstone objects", %{conn: conn} do
  231. tombstone = insert(:tombstone)
  232. uuid = String.split(tombstone.data["id"], "/") |> List.last()
  233. conn =
  234. conn
  235. |> put_req_header("accept", "application/activity+json")
  236. |> get("/objects/#{uuid}")
  237. assert json_response(conn, 404)
  238. end
  239. test "it caches a response", %{conn: conn} do
  240. note = insert(:note)
  241. uuid = String.split(note.data["id"], "/") |> List.last()
  242. conn1 =
  243. conn
  244. |> put_req_header("accept", "application/activity+json")
  245. |> get("/objects/#{uuid}")
  246. assert json_response(conn1, :ok)
  247. assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
  248. conn2 =
  249. conn
  250. |> put_req_header("accept", "application/activity+json")
  251. |> get("/objects/#{uuid}")
  252. assert json_response(conn1, :ok) == json_response(conn2, :ok)
  253. assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
  254. end
  255. test "cached purged after object deletion", %{conn: conn} do
  256. note = insert(:note)
  257. uuid = String.split(note.data["id"], "/") |> List.last()
  258. conn1 =
  259. conn
  260. |> put_req_header("accept", "application/activity+json")
  261. |> get("/objects/#{uuid}")
  262. assert json_response(conn1, :ok)
  263. assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
  264. Object.delete(note)
  265. conn2 =
  266. conn
  267. |> put_req_header("accept", "application/activity+json")
  268. |> get("/objects/#{uuid}")
  269. assert "Not found" == json_response(conn2, :not_found)
  270. end
  271. test "it requires authentication if instance is NOT federating", %{
  272. conn: conn
  273. } do
  274. user = insert(:user)
  275. note = insert(:note)
  276. uuid = String.split(note.data["id"], "/") |> List.last()
  277. conn = put_req_header(conn, "accept", "application/activity+json")
  278. ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user)
  279. end
  280. end
  281. describe "/activities/:uuid" do
  282. test "it returns a json representation of the activity", %{conn: conn} do
  283. activity = insert(:note_activity)
  284. uuid = String.split(activity.data["id"], "/") |> List.last()
  285. conn =
  286. conn
  287. |> put_req_header("accept", "application/activity+json")
  288. |> get("/activities/#{uuid}")
  289. assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
  290. end
  291. test "it returns 404 for non-public activities", %{conn: conn} do
  292. activity = insert(:direct_note_activity)
  293. uuid = String.split(activity.data["id"], "/") |> List.last()
  294. conn =
  295. conn
  296. |> put_req_header("accept", "application/activity+json")
  297. |> get("/activities/#{uuid}")
  298. assert json_response(conn, 404)
  299. end
  300. test "it caches a response", %{conn: conn} do
  301. activity = insert(:note_activity)
  302. uuid = String.split(activity.data["id"], "/") |> List.last()
  303. conn1 =
  304. conn
  305. |> put_req_header("accept", "application/activity+json")
  306. |> get("/activities/#{uuid}")
  307. assert json_response(conn1, :ok)
  308. assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
  309. conn2 =
  310. conn
  311. |> put_req_header("accept", "application/activity+json")
  312. |> get("/activities/#{uuid}")
  313. assert json_response(conn1, :ok) == json_response(conn2, :ok)
  314. assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
  315. end
  316. test "cached purged after activity deletion", %{conn: conn} do
  317. user = insert(:user)
  318. {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
  319. uuid = String.split(activity.data["id"], "/") |> List.last()
  320. conn1 =
  321. conn
  322. |> put_req_header("accept", "application/activity+json")
  323. |> get("/activities/#{uuid}")
  324. assert json_response(conn1, :ok)
  325. assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
  326. Activity.delete_all_by_object_ap_id(activity.object.data["id"])
  327. conn2 =
  328. conn
  329. |> put_req_header("accept", "application/activity+json")
  330. |> get("/activities/#{uuid}")
  331. assert "Not found" == json_response(conn2, :not_found)
  332. end
  333. test "it requires authentication if instance is NOT federating", %{
  334. conn: conn
  335. } do
  336. user = insert(:user)
  337. activity = insert(:note_activity)
  338. uuid = String.split(activity.data["id"], "/") |> List.last()
  339. conn = put_req_header(conn, "accept", "application/activity+json")
  340. ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user)
  341. end
  342. end
  343. describe "/inbox" do
  344. test "it inserts an incoming activity into the database", %{conn: conn} do
  345. data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
  346. conn =
  347. conn
  348. |> assign(:valid_signature, true)
  349. |> put_req_header("content-type", "application/activity+json")
  350. |> post("/inbox", data)
  351. assert "ok" == json_response(conn, 200)
  352. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  353. assert Activity.get_by_ap_id(data["id"])
  354. end
  355. @tag capture_log: true
  356. test "it inserts an incoming activity into the database" <>
  357. "even if we can't fetch the user but have it in our db",
  358. %{conn: conn} do
  359. user =
  360. insert(:user,
  361. ap_id: "https://mastodon.example.org/users/raymoo",
  362. ap_enabled: true,
  363. local: false,
  364. last_refreshed_at: nil
  365. )
  366. data =
  367. File.read!("test/fixtures/mastodon-post-activity.json")
  368. |> Poison.decode!()
  369. |> Map.put("actor", user.ap_id)
  370. |> put_in(["object", "attributedTo"], user.ap_id)
  371. conn =
  372. conn
  373. |> assign(:valid_signature, true)
  374. |> put_req_header("content-type", "application/activity+json")
  375. |> post("/inbox", data)
  376. assert "ok" == json_response(conn, 200)
  377. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  378. assert Activity.get_by_ap_id(data["id"])
  379. end
  380. test "it clears `unreachable` federation status of the sender", %{conn: conn} do
  381. data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
  382. sender_url = data["actor"]
  383. Instances.set_consistently_unreachable(sender_url)
  384. refute Instances.reachable?(sender_url)
  385. conn =
  386. conn
  387. |> assign(:valid_signature, true)
  388. |> put_req_header("content-type", "application/activity+json")
  389. |> post("/inbox", data)
  390. assert "ok" == json_response(conn, 200)
  391. assert Instances.reachable?(sender_url)
  392. end
  393. test "accept follow activity", %{conn: conn} do
  394. Pleroma.Config.put([:instance, :federating], true)
  395. relay = Relay.get_actor()
  396. assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
  397. followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
  398. relay = refresh_record(relay)
  399. accept =
  400. File.read!("test/fixtures/relay/accept-follow.json")
  401. |> String.replace("{{ap_id}}", relay.ap_id)
  402. |> String.replace("{{activity_id}}", activity.data["id"])
  403. assert "ok" ==
  404. conn
  405. |> assign(:valid_signature, true)
  406. |> put_req_header("content-type", "application/activity+json")
  407. |> post("/inbox", accept)
  408. |> json_response(200)
  409. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  410. assert Pleroma.FollowingRelationship.following?(
  411. relay,
  412. followed_relay
  413. )
  414. Mix.shell(Mix.Shell.Process)
  415. on_exit(fn ->
  416. Mix.shell(Mix.Shell.IO)
  417. end)
  418. :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
  419. assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
  420. end
  421. @tag capture_log: true
  422. test "without valid signature, " <>
  423. "it only accepts Create activities and requires enabled federation",
  424. %{conn: conn} do
  425. data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
  426. non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
  427. conn = put_req_header(conn, "content-type", "application/activity+json")
  428. Config.put([:instance, :federating], false)
  429. conn
  430. |> post("/inbox", data)
  431. |> json_response(403)
  432. conn
  433. |> post("/inbox", non_create_data)
  434. |> json_response(403)
  435. Config.put([:instance, :federating], true)
  436. ret_conn = post(conn, "/inbox", data)
  437. assert "ok" == json_response(ret_conn, 200)
  438. conn
  439. |> post("/inbox", non_create_data)
  440. |> json_response(400)
  441. end
  442. end
  443. describe "/users/:nickname/inbox" do
  444. setup do
  445. data =
  446. File.read!("test/fixtures/mastodon-post-activity.json")
  447. |> Poison.decode!()
  448. [data: data]
  449. end
  450. test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
  451. user = insert(:user)
  452. data =
  453. data
  454. |> Map.put("bcc", [user.ap_id])
  455. |> Kernel.put_in(["object", "bcc"], [user.ap_id])
  456. conn =
  457. conn
  458. |> assign(:valid_signature, true)
  459. |> put_req_header("content-type", "application/activity+json")
  460. |> post("/users/#{user.nickname}/inbox", data)
  461. assert "ok" == json_response(conn, 200)
  462. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  463. assert Activity.get_by_ap_id(data["id"])
  464. end
  465. test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
  466. user = insert(:user)
  467. data =
  468. data
  469. |> Map.put("to", user.ap_id)
  470. |> Map.put("cc", [])
  471. |> Kernel.put_in(["object", "to"], user.ap_id)
  472. |> Kernel.put_in(["object", "cc"], [])
  473. conn =
  474. conn
  475. |> assign(:valid_signature, true)
  476. |> put_req_header("content-type", "application/activity+json")
  477. |> post("/users/#{user.nickname}/inbox", data)
  478. assert "ok" == json_response(conn, 200)
  479. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  480. assert Activity.get_by_ap_id(data["id"])
  481. end
  482. test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
  483. user = insert(:user)
  484. data =
  485. data
  486. |> Map.put("to", [])
  487. |> Map.put("cc", user.ap_id)
  488. |> Kernel.put_in(["object", "to"], [])
  489. |> Kernel.put_in(["object", "cc"], user.ap_id)
  490. conn =
  491. conn
  492. |> assign(:valid_signature, true)
  493. |> put_req_header("content-type", "application/activity+json")
  494. |> post("/users/#{user.nickname}/inbox", data)
  495. assert "ok" == json_response(conn, 200)
  496. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  497. %Activity{} = activity = Activity.get_by_ap_id(data["id"])
  498. assert user.ap_id in activity.recipients
  499. end
  500. test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
  501. user = insert(:user)
  502. data =
  503. data
  504. |> Map.put("to", [])
  505. |> Map.put("cc", [])
  506. |> Map.put("bcc", user.ap_id)
  507. |> Kernel.put_in(["object", "to"], [])
  508. |> Kernel.put_in(["object", "cc"], [])
  509. |> Kernel.put_in(["object", "bcc"], user.ap_id)
  510. conn =
  511. conn
  512. |> assign(:valid_signature, true)
  513. |> put_req_header("content-type", "application/activity+json")
  514. |> post("/users/#{user.nickname}/inbox", data)
  515. assert "ok" == json_response(conn, 200)
  516. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  517. assert Activity.get_by_ap_id(data["id"])
  518. end
  519. test "it accepts announces with to as string instead of array", %{conn: conn} do
  520. user = insert(:user)
  521. {:ok, post} = CommonAPI.post(user, %{status: "hey"})
  522. announcer = insert(:user, local: false)
  523. data = %{
  524. "@context" => "https://www.w3.org/ns/activitystreams",
  525. "actor" => announcer.ap_id,
  526. "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
  527. "object" => post.data["object"],
  528. "to" => "https://www.w3.org/ns/activitystreams#Public",
  529. "cc" => [user.ap_id],
  530. "type" => "Announce"
  531. }
  532. conn =
  533. conn
  534. |> assign(:valid_signature, true)
  535. |> put_req_header("content-type", "application/activity+json")
  536. |> post("/users/#{user.nickname}/inbox", data)
  537. assert "ok" == json_response(conn, 200)
  538. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  539. %Activity{} = activity = Activity.get_by_ap_id(data["id"])
  540. assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
  541. end
  542. test "it accepts messages from actors that are followed by the user", %{
  543. conn: conn,
  544. data: data
  545. } do
  546. recipient = insert(:user)
  547. actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
  548. {:ok, recipient} = User.follow(recipient, actor)
  549. object =
  550. data["object"]
  551. |> Map.put("attributedTo", actor.ap_id)
  552. data =
  553. data
  554. |> Map.put("actor", actor.ap_id)
  555. |> Map.put("object", object)
  556. conn =
  557. conn
  558. |> assign(:valid_signature, true)
  559. |> put_req_header("content-type", "application/activity+json")
  560. |> post("/users/#{recipient.nickname}/inbox", data)
  561. assert "ok" == json_response(conn, 200)
  562. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  563. assert Activity.get_by_ap_id(data["id"])
  564. end
  565. test "it rejects reads from other users", %{conn: conn} do
  566. user = insert(:user)
  567. other_user = insert(:user)
  568. conn =
  569. conn
  570. |> assign(:user, other_user)
  571. |> put_req_header("accept", "application/activity+json")
  572. |> get("/users/#{user.nickname}/inbox")
  573. assert json_response(conn, 403)
  574. end
  575. test "it returns a note activity in a collection", %{conn: conn} do
  576. note_activity = insert(:direct_note_activity)
  577. note_object = Object.normalize(note_activity)
  578. user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
  579. conn =
  580. conn
  581. |> assign(:user, user)
  582. |> put_req_header("accept", "application/activity+json")
  583. |> get("/users/#{user.nickname}/inbox?page=true")
  584. assert response(conn, 200) =~ note_object.data["content"]
  585. end
  586. test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
  587. user = insert(:user)
  588. data = Map.put(data, "bcc", [user.ap_id])
  589. sender_host = URI.parse(data["actor"]).host
  590. Instances.set_consistently_unreachable(sender_host)
  591. refute Instances.reachable?(sender_host)
  592. conn =
  593. conn
  594. |> assign(:valid_signature, true)
  595. |> put_req_header("content-type", "application/activity+json")
  596. |> post("/users/#{user.nickname}/inbox", data)
  597. assert "ok" == json_response(conn, 200)
  598. assert Instances.reachable?(sender_host)
  599. end
  600. @tag capture_log: true
  601. test "it removes all follower collections but actor's", %{conn: conn} do
  602. [actor, recipient] = insert_pair(:user)
  603. to = [
  604. recipient.ap_id,
  605. recipient.follower_address,
  606. "https://www.w3.org/ns/activitystreams#Public"
  607. ]
  608. cc = [recipient.follower_address, actor.follower_address]
  609. data = %{
  610. "@context" => ["https://www.w3.org/ns/activitystreams"],
  611. "type" => "Create",
  612. "id" => Utils.generate_activity_id(),
  613. "to" => to,
  614. "cc" => cc,
  615. "actor" => actor.ap_id,
  616. "object" => %{
  617. "type" => "Note",
  618. "to" => to,
  619. "cc" => cc,
  620. "content" => "It's a note",
  621. "attributedTo" => actor.ap_id,
  622. "id" => Utils.generate_object_id()
  623. }
  624. }
  625. conn
  626. |> assign(:valid_signature, true)
  627. |> put_req_header("content-type", "application/activity+json")
  628. |> post("/users/#{recipient.nickname}/inbox", data)
  629. |> json_response(200)
  630. ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
  631. assert activity = Activity.get_by_ap_id(data["id"])
  632. assert activity.id
  633. assert actor.follower_address in activity.recipients
  634. assert actor.follower_address in activity.data["cc"]
  635. refute recipient.follower_address in activity.recipients
  636. refute recipient.follower_address in activity.data["cc"]
  637. refute recipient.follower_address in activity.data["to"]
  638. end
  639. test "it requires authentication", %{conn: conn} do
  640. user = insert(:user)
  641. conn = put_req_header(conn, "accept", "application/activity+json")
  642. ret_conn = get(conn, "/users/#{user.nickname}/inbox")
  643. assert json_response(ret_conn, 403)
  644. ret_conn =
  645. conn
  646. |> assign(:user, user)
  647. |> get("/users/#{user.nickname}/inbox")
  648. assert json_response(ret_conn, 200)
  649. end
  650. end
  651. describe "GET /users/:nickname/outbox" do
  652. test "it paginates correctly", %{conn: conn} do
  653. user = insert(:user)
  654. conn = assign(conn, :user, user)
  655. outbox_endpoint = user.ap_id <> "/outbox"
  656. _posts =
  657. for i <- 0..25 do
  658. {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
  659. activity
  660. end
  661. result =
  662. conn
  663. |> put_req_header("accept", "application/activity+json")
  664. |> get(outbox_endpoint <> "?page=true")
  665. |> json_response(200)
  666. result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
  667. assert length(result["orderedItems"]) == 20
  668. assert length(result_ids) == 20
  669. assert result["next"]
  670. assert String.starts_with?(result["next"], outbox_endpoint)
  671. result_next =
  672. conn
  673. |> put_req_header("accept", "application/activity+json")
  674. |> get(result["next"])
  675. |> json_response(200)
  676. result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
  677. assert length(result_next["orderedItems"]) == 6
  678. assert length(result_next_ids) == 6
  679. refute Enum.find(result_next_ids, fn x -> x in result_ids end)
  680. refute Enum.find(result_ids, fn x -> x in result_next_ids end)
  681. assert String.starts_with?(result["id"], outbox_endpoint)
  682. result_next_again =
  683. conn
  684. |> put_req_header("accept", "application/activity+json")
  685. |> get(result_next["id"])
  686. |> json_response(200)
  687. assert result_next == result_next_again
  688. end
  689. test "it returns 200 even if there're no activities", %{conn: conn} do
  690. user = insert(:user)
  691. outbox_endpoint = user.ap_id <> "/outbox"
  692. conn =
  693. conn
  694. |> assign(:user, user)
  695. |> put_req_header("accept", "application/activity+json")
  696. |> get(outbox_endpoint)
  697. result = json_response(conn, 200)
  698. assert outbox_endpoint == result["id"]
  699. end
  700. test "it returns a note activity in a collection", %{conn: conn} do
  701. note_activity = insert(:note_activity)
  702. note_object = Object.normalize(note_activity)
  703. user = User.get_cached_by_ap_id(note_activity.data["actor"])
  704. conn =
  705. conn
  706. |> assign(:user, user)
  707. |> put_req_header("accept", "application/activity+json")
  708. |> get("/users/#{user.nickname}/outbox?page=true")
  709. assert response(conn, 200) =~ note_object.data["content"]
  710. end
  711. test "it returns an announce activity in a collection", %{conn: conn} do
  712. announce_activity = insert(:announce_activity)
  713. user = User.get_cached_by_ap_id(announce_activity.data["actor"])
  714. conn =
  715. conn
  716. |> assign(:user, user)
  717. |> put_req_header("accept", "application/activity+json")
  718. |> get("/users/#{user.nickname}/outbox?page=true")
  719. assert response(conn, 200) =~ announce_activity.data["object"]
  720. end
  721. test "it requires authentication if instance is NOT federating", %{
  722. conn: conn
  723. } do
  724. user = insert(:user)
  725. conn = put_req_header(conn, "accept", "application/activity+json")
  726. ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
  727. end
  728. end
  729. describe "POST /users/:nickname/outbox (C2S)" do
  730. setup do: clear_config([:instance, :limit])
  731. setup do
  732. [
  733. activity: %{
  734. "@context" => "https://www.w3.org/ns/activitystreams",
  735. "type" => "Create",
  736. "object" => %{"type" => "Note", "content" => "AP C2S test"},
  737. "to" => "https://www.w3.org/ns/activitystreams#Public",
  738. "cc" => []
  739. }
  740. ]
  741. end
  742. test "it rejects posts from other users / unauthenticated users", %{
  743. conn: conn,
  744. activity: activity
  745. } do
  746. user = insert(:user)
  747. other_user = insert(:user)
  748. conn = put_req_header(conn, "content-type", "application/activity+json")
  749. conn
  750. |> post("/users/#{user.nickname}/outbox", activity)
  751. |> json_response(403)
  752. conn
  753. |> assign(:user, other_user)
  754. |> post("/users/#{user.nickname}/outbox", activity)
  755. |> json_response(403)
  756. end
  757. test "it inserts an incoming create activity into the database", %{
  758. conn: conn,
  759. activity: activity
  760. } do
  761. user = insert(:user)
  762. result =
  763. conn
  764. |> assign(:user, user)
  765. |> put_req_header("content-type", "application/activity+json")
  766. |> post("/users/#{user.nickname}/outbox", activity)
  767. |> json_response(201)
  768. assert Activity.get_by_ap_id(result["id"])
  769. assert result["object"]
  770. assert %Object{data: object} = Object.normalize(result["object"])
  771. assert object["content"] == activity["object"]["content"]
  772. end
  773. test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
  774. user = insert(:user)
  775. activity =
  776. activity
  777. |> put_in(["object", "type"], "Benis")
  778. _result =
  779. conn
  780. |> assign(:user, user)
  781. |> put_req_header("content-type", "application/activity+json")
  782. |> post("/users/#{user.nickname}/outbox", activity)
  783. |> json_response(400)
  784. end
  785. test "it inserts an incoming sensitive activity into the database", %{
  786. conn: conn,
  787. activity: activity
  788. } do
  789. user = insert(:user)
  790. conn = assign(conn, :user, user)
  791. object = Map.put(activity["object"], "sensitive", true)
  792. activity = Map.put(activity, "object", object)
  793. response =
  794. conn
  795. |> put_req_header("content-type", "application/activity+json")
  796. |> post("/users/#{user.nickname}/outbox", activity)
  797. |> json_response(201)
  798. assert Activity.get_by_ap_id(response["id"])
  799. assert response["object"]
  800. assert %Object{data: response_object} = Object.normalize(response["object"])
  801. assert response_object["sensitive"] == true
  802. assert response_object["content"] == activity["object"]["content"]
  803. representation =
  804. conn
  805. |> put_req_header("accept", "application/activity+json")
  806. |> get(response["id"])
  807. |> json_response(200)
  808. assert representation["object"]["sensitive"] == true
  809. end
  810. test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
  811. user = insert(:user)
  812. activity = Map.put(activity, "type", "BadType")
  813. conn =
  814. conn
  815. |> assign(:user, user)
  816. |> put_req_header("content-type", "application/activity+json")
  817. |> post("/users/#{user.nickname}/outbox", activity)
  818. assert json_response(conn, 400)
  819. end
  820. test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
  821. note_activity = insert(:note_activity)
  822. note_object = Object.normalize(note_activity)
  823. user = User.get_cached_by_ap_id(note_activity.data["actor"])
  824. data = %{
  825. type: "Delete",
  826. object: %{
  827. id: note_object.data["id"]
  828. }
  829. }
  830. conn =
  831. conn
  832. |> assign(:user, user)
  833. |> put_req_header("content-type", "application/activity+json")
  834. |> post("/users/#{user.nickname}/outbox", data)
  835. result = json_response(conn, 201)
  836. assert Activity.get_by_ap_id(result["id"])
  837. assert object = Object.get_by_ap_id(note_object.data["id"])
  838. assert object.data["type"] == "Tombstone"
  839. end
  840. test "it rejects delete activity of object from other actor", %{conn: conn} do
  841. note_activity = insert(:note_activity)
  842. note_object = Object.normalize(note_activity)
  843. user = insert(:user)
  844. data = %{
  845. type: "Delete",
  846. object: %{
  847. id: note_object.data["id"]
  848. }
  849. }
  850. conn =
  851. conn
  852. |> assign(:user, user)
  853. |> put_req_header("content-type", "application/activity+json")
  854. |> post("/users/#{user.nickname}/outbox", data)
  855. assert json_response(conn, 400)
  856. end
  857. test "it increases like count when receiving a like action", %{conn: conn} do
  858. note_activity = insert(:note_activity)
  859. note_object = Object.normalize(note_activity)
  860. user = User.get_cached_by_ap_id(note_activity.data["actor"])
  861. data = %{
  862. type: "Like",
  863. object: %{
  864. id: note_object.data["id"]
  865. }
  866. }
  867. conn =
  868. conn
  869. |> assign(:user, user)
  870. |> put_req_header("content-type", "application/activity+json")
  871. |> post("/users/#{user.nickname}/outbox", data)
  872. result = json_response(conn, 201)
  873. assert Activity.get_by_ap_id(result["id"])
  874. assert object = Object.get_by_ap_id(note_object.data["id"])
  875. assert object.data["like_count"] == 1
  876. end
  877. test "it doesn't spreads faulty attributedTo or actor fields", %{
  878. conn: conn,
  879. activity: activity
  880. } do
  881. reimu = insert(:user, nickname: "reimu")
  882. cirno = insert(:user, nickname: "cirno")
  883. assert reimu.ap_id
  884. assert cirno.ap_id
  885. activity =
  886. activity
  887. |> put_in(["object", "actor"], reimu.ap_id)
  888. |> put_in(["object", "attributedTo"], reimu.ap_id)
  889. |> put_in(["actor"], reimu.ap_id)
  890. |> put_in(["attributedTo"], reimu.ap_id)
  891. _reimu_outbox =
  892. conn
  893. |> assign(:user, cirno)
  894. |> put_req_header("content-type", "application/activity+json")
  895. |> post("/users/#{reimu.nickname}/outbox", activity)
  896. |> json_response(403)
  897. cirno_outbox =
  898. conn
  899. |> assign(:user, cirno)
  900. |> put_req_header("content-type", "application/activity+json")
  901. |> post("/users/#{cirno.nickname}/outbox", activity)
  902. |> json_response(201)
  903. assert cirno_outbox["attributedTo"] == nil
  904. assert cirno_outbox["actor"] == cirno.ap_id
  905. assert cirno_object = Object.normalize(cirno_outbox["object"])
  906. assert cirno_object.data["actor"] == cirno.ap_id
  907. assert cirno_object.data["attributedTo"] == cirno.ap_id
  908. end
  909. test "Character limitation", %{conn: conn, activity: activity} do
  910. Pleroma.Config.put([:instance, :limit], 5)
  911. user = insert(:user)
  912. result =
  913. conn
  914. |> assign(:user, user)
  915. |> put_req_header("content-type", "application/activity+json")
  916. |> post("/users/#{user.nickname}/outbox", activity)
  917. |> json_response(400)
  918. assert result == "Note is over the character limit"
  919. end
  920. end
  921. describe "/relay/followers" do
  922. test "it returns relay followers", %{conn: conn} do
  923. relay_actor = Relay.get_actor()
  924. user = insert(:user)
  925. User.follow(user, relay_actor)
  926. result =
  927. conn
  928. |> get("/relay/followers")
  929. |> json_response(200)
  930. assert result["first"]["orderedItems"] == [user.ap_id]
  931. end
  932. test "on non-federating instance, it returns 404", %{conn: conn} do
  933. Config.put([:instance, :federating], false)
  934. user = insert(:user)
  935. conn
  936. |> assign(:user, user)
  937. |> get("/relay/followers")
  938. |> json_response(404)
  939. end
  940. end
  941. describe "/relay/following" do
  942. test "it returns relay following", %{conn: conn} do
  943. result =
  944. conn
  945. |> get("/relay/following")
  946. |> json_response(200)
  947. assert result["first"]["orderedItems"] == []
  948. end
  949. test "on non-federating instance, it returns 404", %{conn: conn} do
  950. Config.put([:instance, :federating], false)
  951. user = insert(:user)
  952. conn
  953. |> assign(:user, user)
  954. |> get("/relay/following")
  955. |> json_response(404)
  956. end
  957. end
  958. describe "/users/:nickname/followers" do
  959. test "it returns the followers in a collection", %{conn: conn} do
  960. user = insert(:user)
  961. user_two = insert(:user)
  962. User.follow(user, user_two)
  963. result =
  964. conn
  965. |> assign(:user, user_two)
  966. |> get("/users/#{user_two.nickname}/followers")
  967. |> json_response(200)
  968. assert result["first"]["orderedItems"] == [user.ap_id]
  969. end
  970. test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
  971. user = insert(:user)
  972. user_two = insert(:user, hide_followers: true)
  973. User.follow(user, user_two)
  974. result =
  975. conn
  976. |> assign(:user, user)
  977. |> get("/users/#{user_two.nickname}/followers")
  978. |> json_response(200)
  979. assert is_binary(result["first"])
  980. end
  981. test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
  982. %{conn: conn} do
  983. user = insert(:user)
  984. other_user = insert(:user, hide_followers: true)
  985. result =
  986. conn
  987. |> assign(:user, user)
  988. |> get("/users/#{other_user.nickname}/followers?page=1")
  989. assert result.status == 403
  990. assert result.resp_body == ""
  991. end
  992. test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
  993. %{conn: conn} do
  994. user = insert(:user, hide_followers: true)
  995. other_user = insert(:user)
  996. {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
  997. result =
  998. conn
  999. |> assign(:user, user)
  1000. |> get("/users/#{user.nickname}/followers?page=1")
  1001. |> json_response(200)
  1002. assert result["totalItems"] == 1
  1003. assert result["orderedItems"] == [other_user.ap_id]
  1004. end
  1005. test "it works for more than 10 users", %{conn: conn} do
  1006. user = insert(:user)
  1007. Enum.each(1..15, fn _ ->
  1008. other_user = insert(:user)
  1009. User.follow(other_user, user)
  1010. end)
  1011. result =
  1012. conn
  1013. |> assign(:user, user)
  1014. |> get("/users/#{user.nickname}/followers")
  1015. |> json_response(200)
  1016. assert length(result["first"]["orderedItems"]) == 10
  1017. assert result["first"]["totalItems"] == 15
  1018. assert result["totalItems"] == 15
  1019. result =
  1020. conn
  1021. |> assign(:user, user)
  1022. |> get("/users/#{user.nickname}/followers?page=2")
  1023. |> json_response(200)
  1024. assert length(result["orderedItems"]) == 5
  1025. assert result["totalItems"] == 15
  1026. end
  1027. test "does not require authentication", %{conn: conn} do
  1028. user = insert(:user)
  1029. conn
  1030. |> get("/users/#{user.nickname}/followers")
  1031. |> json_response(200)
  1032. end
  1033. end
  1034. describe "/users/:nickname/following" do
  1035. test "it returns the following in a collection", %{conn: conn} do
  1036. user = insert(:user)
  1037. user_two = insert(:user)
  1038. User.follow(user, user_two)
  1039. result =
  1040. conn
  1041. |> assign(:user, user)
  1042. |> get("/users/#{user.nickname}/following")
  1043. |> json_response(200)
  1044. assert result["first"]["orderedItems"] == [user_two.ap_id]
  1045. end
  1046. test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
  1047. user = insert(:user)
  1048. user_two = insert(:user, hide_follows: true)
  1049. User.follow(user, user_two)
  1050. result =
  1051. conn
  1052. |> assign(:user, user)
  1053. |> get("/users/#{user_two.nickname}/following")
  1054. |> json_response(200)
  1055. assert is_binary(result["first"])
  1056. end
  1057. test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
  1058. %{conn: conn} do
  1059. user = insert(:user)
  1060. user_two = insert(:user, hide_follows: true)
  1061. result =
  1062. conn
  1063. |> assign(:user, user)
  1064. |> get("/users/#{user_two.nickname}/following?page=1")
  1065. assert result.status == 403
  1066. assert result.resp_body == ""
  1067. end
  1068. test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
  1069. %{conn: conn} do
  1070. user = insert(:user, hide_follows: true)
  1071. other_user = insert(:user)
  1072. {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
  1073. result =
  1074. conn
  1075. |> assign(:user, user)
  1076. |> get("/users/#{user.nickname}/following?page=1")
  1077. |> json_response(200)
  1078. assert result["totalItems"] == 1
  1079. assert result["orderedItems"] == [other_user.ap_id]
  1080. end
  1081. test "it works for more than 10 users", %{conn: conn} do
  1082. user = insert(:user)
  1083. Enum.each(1..15, fn _ ->
  1084. user = User.get_cached_by_id(user.id)
  1085. other_user = insert(:user)
  1086. User.follow(user, other_user)
  1087. end)
  1088. result =
  1089. conn
  1090. |> assign(:user, user)
  1091. |> get("/users/#{user.nickname}/following")
  1092. |> json_response(200)
  1093. assert length(result["first"]["orderedItems"]) == 10
  1094. assert result["first"]["totalItems"] == 15
  1095. assert result["totalItems"] == 15
  1096. result =
  1097. conn
  1098. |> assign(:user, user)
  1099. |> get("/users/#{user.nickname}/following?page=2")
  1100. |> json_response(200)
  1101. assert length(result["orderedItems"]) == 5
  1102. assert result["totalItems"] == 15
  1103. end
  1104. test "does not require authentication", %{conn: conn} do
  1105. user = insert(:user)
  1106. conn
  1107. |> get("/users/#{user.nickname}/following")
  1108. |> json_response(200)
  1109. end
  1110. end
  1111. describe "delivery tracking" do
  1112. test "it tracks a signed object fetch", %{conn: conn} do
  1113. user = insert(:user, local: false)
  1114. activity = insert(:note_activity)
  1115. object = Object.normalize(activity)
  1116. object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
  1117. conn
  1118. |> put_req_header("accept", "application/activity+json")
  1119. |> assign(:user, user)
  1120. |> get(object_path)
  1121. |> json_response(200)
  1122. assert Delivery.get(object.id, user.id)
  1123. end
  1124. test "it tracks a signed activity fetch", %{conn: conn} do
  1125. user = insert(:user, local: false)
  1126. activity = insert(:note_activity)
  1127. object = Object.normalize(activity)
  1128. activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
  1129. conn
  1130. |> put_req_header("accept", "application/activity+json")
  1131. |> assign(:user, user)
  1132. |> get(activity_path)
  1133. |> json_response(200)
  1134. assert Delivery.get(object.id, user.id)
  1135. end
  1136. test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
  1137. user = insert(:user, local: false)
  1138. other_user = insert(:user, local: false)
  1139. activity = insert(:note_activity)
  1140. object = Object.normalize(activity)
  1141. object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
  1142. conn
  1143. |> put_req_header("accept", "application/activity+json")
  1144. |> assign(:user, user)
  1145. |> get(object_path)
  1146. |> json_response(200)
  1147. build_conn()
  1148. |> put_req_header("accept", "application/activity+json")
  1149. |> assign(:user, other_user)
  1150. |> get(object_path)
  1151. |> json_response(200)
  1152. assert Delivery.get(object.id, user.id)
  1153. assert Delivery.get(object.id, other_user.id)
  1154. end
  1155. test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
  1156. user = insert(:user, local: false)
  1157. other_user = insert(:user, local: false)
  1158. activity = insert(:note_activity)
  1159. object = Object.normalize(activity)
  1160. activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
  1161. conn
  1162. |> put_req_header("accept", "application/activity+json")
  1163. |> assign(:user, user)
  1164. |> get(activity_path)
  1165. |> json_response(200)
  1166. build_conn()
  1167. |> put_req_header("accept", "application/activity+json")
  1168. |> assign(:user, other_user)
  1169. |> get(activity_path)
  1170. |> json_response(200)
  1171. assert Delivery.get(object.id, user.id)
  1172. assert Delivery.get(object.id, other_user.id)
  1173. end
  1174. end
  1175. describe "Additional ActivityPub C2S endpoints" do
  1176. test "GET /api/ap/whoami", %{conn: conn} do
  1177. user = insert(:user)
  1178. conn =
  1179. conn
  1180. |> assign(:user, user)
  1181. |> get("/api/ap/whoami")
  1182. user = User.get_cached_by_id(user.id)
  1183. assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
  1184. conn
  1185. |> get("/api/ap/whoami")
  1186. |> json_response(403)
  1187. end
  1188. setup do: clear_config([:media_proxy])
  1189. setup do: clear_config([Pleroma.Upload])
  1190. test "POST /api/ap/upload_media", %{conn: conn} do
  1191. user = insert(:user)
  1192. desc = "Description of the image"
  1193. image = %Plug.Upload{
  1194. content_type: "bad/content-type",
  1195. path: Path.absname("test/fixtures/image.jpg"),
  1196. filename: "an_image.png"
  1197. }
  1198. object =
  1199. conn
  1200. |> assign(:user, user)
  1201. |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
  1202. |> json_response(:created)
  1203. assert object["name"] == desc
  1204. assert object["type"] == "Document"
  1205. assert object["actor"] == user.ap_id
  1206. assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
  1207. assert is_binary(object_href)
  1208. assert object_mediatype == "image/jpeg"
  1209. assert String.ends_with?(object_href, ".jpg")
  1210. activity_request = %{
  1211. "@context" => "https://www.w3.org/ns/activitystreams",
  1212. "type" => "Create",
  1213. "object" => %{
  1214. "type" => "Note",
  1215. "content" => "AP C2S test, attachment",
  1216. "attachment" => [object]
  1217. },
  1218. "to" => "https://www.w3.org/ns/activitystreams#Public",
  1219. "cc" => []
  1220. }
  1221. activity_response =
  1222. conn
  1223. |> assign(:user, user)
  1224. |> post("/users/#{user.nickname}/outbox", activity_request)
  1225. |> json_response(:created)
  1226. assert activity_response["id"]
  1227. assert activity_response["object"]
  1228. assert activity_response["actor"] == user.ap_id
  1229. assert %Object{data: %{"attachment" => [attachment]}} =
  1230. Object.normalize(activity_response["object"])
  1231. assert attachment["type"] == "Document"
  1232. assert attachment["name"] == desc
  1233. assert [
  1234. %{
  1235. "href" => ^object_href,
  1236. "type" => "Link",
  1237. "mediaType" => ^object_mediatype
  1238. }
  1239. ] = attachment["url"]
  1240. # Fails if unauthenticated
  1241. conn
  1242. |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
  1243. |> json_response(403)
  1244. end
  1245. end
  1246. end