logo

pleroma

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

transmogrifier_test.exs (20784B)


  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.TransmogrifierTest do
  5. use Oban.Testing, repo: Pleroma.Repo
  6. use Pleroma.DataCase
  7. alias Pleroma.Activity
  8. alias Pleroma.Object
  9. alias Pleroma.User
  10. alias Pleroma.Web.ActivityPub.Transmogrifier
  11. alias Pleroma.Web.ActivityPub.Utils
  12. alias Pleroma.Web.AdminAPI.AccountView
  13. alias Pleroma.Web.CommonAPI
  14. import Mock
  15. import Pleroma.Factory
  16. import ExUnit.CaptureLog
  17. setup_all do
  18. Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
  19. :ok
  20. end
  21. setup do: clear_config([:instance, :max_remote_account_fields])
  22. describe "handle_incoming" do
  23. test "it works for incoming unfollows with an existing follow" do
  24. user = insert(:user)
  25. follow_data =
  26. File.read!("test/fixtures/mastodon-follow-activity.json")
  27. |> Jason.decode!()
  28. |> Map.put("object", user.ap_id)
  29. {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
  30. data =
  31. File.read!("test/fixtures/mastodon-unfollow-activity.json")
  32. |> Jason.decode!()
  33. |> Map.put("object", follow_data)
  34. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  35. assert data["type"] == "Undo"
  36. assert data["object"]["type"] == "Follow"
  37. assert data["object"]["object"] == user.ap_id
  38. assert data["actor"] == "http://mastodon.example.org/users/admin"
  39. refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
  40. end
  41. test "it accepts Flag activities" do
  42. user = insert(:user)
  43. other_user = insert(:user)
  44. {:ok, activity} = CommonAPI.post(user, %{status: "test post"})
  45. object = Object.normalize(activity, fetch: false)
  46. note_obj = %{
  47. "type" => "Note",
  48. "id" => activity.object.data["id"],
  49. "content" => "test post",
  50. "published" => object.data["published"],
  51. "actor" => AccountView.render("show.json", %{user: user, skip_visibility_check: true})
  52. }
  53. message = %{
  54. "@context" => "https://www.w3.org/ns/activitystreams",
  55. "cc" => [user.ap_id],
  56. "object" => [user.ap_id, activity.data["id"]],
  57. "type" => "Flag",
  58. "content" => "blocked AND reported!!!",
  59. "actor" => other_user.ap_id
  60. }
  61. assert {:ok, activity} = Transmogrifier.handle_incoming(message)
  62. assert activity.data["object"] == [user.ap_id, note_obj]
  63. assert activity.data["content"] == "blocked AND reported!!!"
  64. assert activity.data["actor"] == other_user.ap_id
  65. assert activity.data["cc"] == [user.ap_id]
  66. end
  67. test "it accepts Move activities" do
  68. old_user = insert(:user)
  69. new_user = insert(:user)
  70. message = %{
  71. "@context" => "https://www.w3.org/ns/activitystreams",
  72. "type" => "Move",
  73. "actor" => old_user.ap_id,
  74. "object" => old_user.ap_id,
  75. "target" => new_user.ap_id
  76. }
  77. assert :error = Transmogrifier.handle_incoming(message)
  78. {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
  79. assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
  80. assert activity.actor == old_user.ap_id
  81. assert activity.data["actor"] == old_user.ap_id
  82. assert activity.data["object"] == old_user.ap_id
  83. assert activity.data["target"] == new_user.ap_id
  84. assert activity.data["type"] == "Move"
  85. end
  86. test "it fixes both the Create and object contexts in a reply" do
  87. insert(:user, ap_id: "https://mk.absturztau.be/users/8ozbzjs3o8")
  88. insert(:user, ap_id: "https://p.helene.moe/users/helene")
  89. create_activity =
  90. "test/fixtures/create-pleroma-reply-to-misskey-thread.json"
  91. |> File.read!()
  92. |> Jason.decode!()
  93. assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(create_activity)
  94. object = Object.normalize(activity, fetch: false)
  95. assert activity.data["context"] == object.data["context"]
  96. end
  97. test "it keeps link tags" do
  98. insert(:user, ap_id: "https://example.org/users/alice")
  99. message = File.read!("test/fixtures/fep-e232.json") |> Jason.decode!()
  100. assert capture_log(fn ->
  101. assert {:ok, activity} = Transmogrifier.handle_incoming(message)
  102. object = Object.normalize(activity)
  103. assert [%{"type" => "Mention"}, %{"type" => "Link"}] = object.data["tag"]
  104. end) =~ "Object rejected while fetching"
  105. end
  106. test "it accepts quote posts" do
  107. insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i")
  108. object = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!()
  109. message = %{
  110. "@context" => "https://www.w3.org/ns/activitystreams",
  111. "type" => "Create",
  112. "actor" => "https://misskey.io/users/7rkrarq81i",
  113. "object" => object
  114. }
  115. assert {:ok, activity} = Transmogrifier.handle_incoming(message)
  116. # Object was created in the database
  117. object = Object.normalize(activity)
  118. assert object.data["quoteUrl"] == "https://misskey.io/notes/8vs6wxufd0"
  119. # It fetched the quoted post
  120. assert Object.normalize("https://misskey.io/notes/8vs6wxufd0")
  121. end
  122. end
  123. describe "prepare outgoing" do
  124. test "it inlines private announced objects" do
  125. user = insert(:user)
  126. {:ok, activity} = CommonAPI.post(user, %{status: "hey", visibility: "private"})
  127. {:ok, announce_activity} = CommonAPI.repeat(activity.id, user)
  128. {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data)
  129. assert modified["object"]["content"] == "hey"
  130. assert modified["object"]["actor"] == modified["object"]["attributedTo"]
  131. end
  132. test "it turns mentions into tags" do
  133. user = insert(:user)
  134. other_user = insert(:user)
  135. {:ok, activity} =
  136. CommonAPI.post(user, %{status: "hey, @#{other_user.nickname}, how are ya? #2hu"})
  137. with_mock Pleroma.Notification,
  138. get_notified_from_activity: fn _, _ -> [] end do
  139. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  140. object = modified["object"]
  141. expected_mention = %{
  142. "href" => other_user.ap_id,
  143. "name" => "@#{other_user.nickname}",
  144. "type" => "Mention"
  145. }
  146. expected_tag = %{
  147. "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
  148. "type" => "Hashtag",
  149. "name" => "#2hu"
  150. }
  151. refute called(Pleroma.Notification.get_notified_from_activity(:_, :_))
  152. assert Enum.member?(object["tag"], expected_tag)
  153. assert Enum.member?(object["tag"], expected_mention)
  154. end
  155. end
  156. test "it adds the json-ld context and the conversation property" do
  157. user = insert(:user)
  158. {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
  159. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  160. assert modified["@context"] == Utils.make_json_ld_header()["@context"]
  161. assert modified["object"]["conversation"] == modified["context"]
  162. end
  163. test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
  164. user = insert(:user)
  165. {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
  166. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  167. assert modified["object"]["actor"] == modified["object"]["attributedTo"]
  168. end
  169. test "it strips internal hashtag data" do
  170. user = insert(:user)
  171. {:ok, activity} = CommonAPI.post(user, %{status: "#2hu"})
  172. expected_tag = %{
  173. "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
  174. "type" => "Hashtag",
  175. "name" => "#2hu"
  176. }
  177. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  178. assert modified["object"]["tag"] == [expected_tag]
  179. end
  180. test "it strips internal fields" do
  181. user = insert(:user)
  182. {:ok, activity} =
  183. CommonAPI.post(user, %{
  184. status: "#2hu :firefox:",
  185. generator: %{type: "Application", name: "TestClient", url: "https://pleroma.social"}
  186. })
  187. # Ensure injected application data made it into the activity
  188. # as we don't have a Token to derive it from, otherwise it will
  189. # be nil and the test will pass
  190. assert %{
  191. type: "Application",
  192. name: "TestClient",
  193. url: "https://pleroma.social"
  194. } == activity.object.data["generator"]
  195. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  196. assert length(modified["object"]["tag"]) == 2
  197. assert is_nil(modified["object"]["emoji"])
  198. assert is_nil(modified["object"]["like_count"])
  199. assert is_nil(modified["object"]["announcements"])
  200. assert is_nil(modified["object"]["announcement_count"])
  201. assert is_nil(modified["object"]["generator"])
  202. end
  203. test "it strips internal fields of article" do
  204. activity = insert(:article_activity)
  205. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  206. assert length(modified["object"]["tag"]) == 2
  207. assert is_nil(modified["object"]["emoji"])
  208. assert is_nil(modified["object"]["like_count"])
  209. assert is_nil(modified["object"]["announcements"])
  210. assert is_nil(modified["object"]["announcement_count"])
  211. assert is_nil(modified["object"]["likes"])
  212. end
  213. test "the directMessage flag is present" do
  214. user = insert(:user)
  215. other_user = insert(:user)
  216. {:ok, activity} = CommonAPI.post(user, %{status: "2hu :moominmamma:"})
  217. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  218. assert modified["directMessage"] == false
  219. {:ok, activity} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"})
  220. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  221. assert modified["directMessage"] == false
  222. {:ok, activity} =
  223. CommonAPI.post(user, %{
  224. status: "@#{other_user.nickname} :moominmamma:",
  225. visibility: "direct"
  226. })
  227. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  228. assert modified["directMessage"] == true
  229. end
  230. test "it strips BCC field" do
  231. user = insert(:user)
  232. {:ok, list} = Pleroma.List.create("foo", user)
  233. {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
  234. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  235. assert is_nil(modified["bcc"])
  236. end
  237. test "it can handle Listen activities" do
  238. listen_activity = insert(:listen)
  239. {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data)
  240. assert modified["type"] == "Listen"
  241. user = insert(:user)
  242. {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"})
  243. {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data)
  244. end
  245. test "custom emoji urls are URI encoded" do
  246. # :dinosaur: filename has a space -> dino walking.gif
  247. user = insert(:user)
  248. {:ok, activity} = CommonAPI.post(user, %{status: "everybody do the dinosaur :dinosaur:"})
  249. {:ok, prepared} = Transmogrifier.prepare_outgoing(activity.data)
  250. assert length(prepared["object"]["tag"]) == 1
  251. url = prepared["object"]["tag"] |> List.first() |> Map.get("icon") |> Map.get("url")
  252. assert url == "http://localhost:4001/emoji/dino%20walking.gif"
  253. end
  254. test "Updates of Notes are handled" do
  255. user = insert(:user)
  256. {:ok, activity} = CommonAPI.post(user, %{status: "everybody do the dinosaur :dinosaur:"})
  257. {:ok, update} = CommonAPI.update(user, activity, %{status: "mew mew :blank:"})
  258. {:ok, prepared} = Transmogrifier.prepare_outgoing(update.data)
  259. assert %{
  260. "content" => "mew mew :blank:",
  261. "tag" => [%{"name" => ":blank:", "type" => "Emoji"}],
  262. "formerRepresentations" => %{
  263. "orderedItems" => [
  264. %{
  265. "content" => "everybody do the dinosaur :dinosaur:",
  266. "tag" => [%{"name" => ":dinosaur:", "type" => "Emoji"}]
  267. }
  268. ]
  269. }
  270. } = prepared["object"]
  271. end
  272. test "it prepares a quote post" do
  273. user = insert(:user)
  274. {:ok, quoted_post} = CommonAPI.post(user, %{status: "hey"})
  275. {:ok, quote_post} = CommonAPI.post(user, %{status: "hey", quote_id: quoted_post.id})
  276. {:ok, modified} = Transmogrifier.prepare_outgoing(quote_post.data)
  277. %{data: %{"id" => quote_id}} = Object.normalize(quoted_post)
  278. assert modified["object"]["quoteUrl"] == quote_id
  279. assert modified["object"]["quoteUri"] == quote_id
  280. end
  281. end
  282. describe "actor rewriting" do
  283. test "it fixes the actor URL property to be a proper URI" do
  284. data = %{
  285. "url" => %{"href" => "http://example.com"}
  286. }
  287. rewritten = Transmogrifier.maybe_fix_user_object(data)
  288. assert rewritten["url"] == "http://example.com"
  289. end
  290. end
  291. describe "actor origin containment" do
  292. test "it rejects activities which reference objects with bogus origins" do
  293. data = %{
  294. "@context" => "https://www.w3.org/ns/activitystreams",
  295. "id" => "http://mastodon.example.org/users/admin/activities/1234",
  296. "actor" => "http://mastodon.example.org/users/admin",
  297. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  298. "object" => "https://info.pleroma.site/activity.json",
  299. "type" => "Announce"
  300. }
  301. assert capture_log(fn ->
  302. {:error, _} = Transmogrifier.handle_incoming(data)
  303. end) =~ "Object rejected while fetching"
  304. end
  305. test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
  306. data = %{
  307. "@context" => "https://www.w3.org/ns/activitystreams",
  308. "id" => "http://mastodon.example.org/users/admin/activities/1234",
  309. "actor" => "http://mastodon.example.org/users/admin",
  310. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  311. "object" => "https://info.pleroma.site/activity2.json",
  312. "type" => "Announce"
  313. }
  314. assert capture_log(fn ->
  315. {:error, _} = Transmogrifier.handle_incoming(data)
  316. end) =~ "Object rejected while fetching"
  317. end
  318. test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
  319. data = %{
  320. "@context" => "https://www.w3.org/ns/activitystreams",
  321. "id" => "http://mastodon.example.org/users/admin/activities/1234",
  322. "actor" => "http://mastodon.example.org/users/admin",
  323. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  324. "object" => "https://info.pleroma.site/activity3.json",
  325. "type" => "Announce"
  326. }
  327. assert capture_log(fn ->
  328. {:error, _} = Transmogrifier.handle_incoming(data)
  329. end) =~ "Object rejected while fetching"
  330. end
  331. end
  332. describe "fix_explicit_addressing" do
  333. setup do
  334. user = insert(:user)
  335. [user: user]
  336. end
  337. test "moves non-explicitly mentioned actors to cc", %{user: user} do
  338. explicitly_mentioned_actors = [
  339. "https://pleroma.gold/users/user1",
  340. "https://pleroma.gold/user2"
  341. ]
  342. object = %{
  343. "actor" => user.ap_id,
  344. "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
  345. "cc" => [],
  346. "tag" =>
  347. Enum.map(explicitly_mentioned_actors, fn href ->
  348. %{"type" => "Mention", "href" => href}
  349. end)
  350. }
  351. fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
  352. assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
  353. refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
  354. assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
  355. end
  356. test "does not move actor's follower collection to cc", %{user: user} do
  357. object = %{
  358. "actor" => user.ap_id,
  359. "to" => [user.follower_address],
  360. "cc" => []
  361. }
  362. fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
  363. assert user.follower_address in fixed_object["to"]
  364. refute user.follower_address in fixed_object["cc"]
  365. end
  366. test "removes recipient's follower collection from cc", %{user: user} do
  367. recipient = insert(:user)
  368. object = %{
  369. "actor" => user.ap_id,
  370. "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
  371. "cc" => [user.follower_address, recipient.follower_address]
  372. }
  373. fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
  374. assert user.follower_address in fixed_object["cc"]
  375. refute recipient.follower_address in fixed_object["cc"]
  376. refute recipient.follower_address in fixed_object["to"]
  377. end
  378. end
  379. describe "fix_summary/1" do
  380. test "returns fixed object" do
  381. assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
  382. assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
  383. assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
  384. end
  385. end
  386. describe "fix_url/1" do
  387. test "fixes data for object when url is map" do
  388. object = %{
  389. "url" => %{
  390. "type" => "Link",
  391. "mimeType" => "video/mp4",
  392. "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
  393. }
  394. }
  395. assert Transmogrifier.fix_url(object) == %{
  396. "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
  397. }
  398. end
  399. test "returns non-modified object" do
  400. assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
  401. end
  402. end
  403. describe "get_obj_helper/2" do
  404. test "returns nil when cannot normalize object" do
  405. assert capture_log(fn ->
  406. refute Transmogrifier.get_obj_helper("test-obj-id")
  407. end) =~ "Unsupported URI scheme"
  408. end
  409. @tag capture_log: true
  410. test "returns {:ok, %Object{}} for success case" do
  411. assert {:ok, %Object{}} =
  412. Transmogrifier.get_obj_helper(
  413. "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
  414. )
  415. end
  416. end
  417. describe "fix_attachments/1" do
  418. test "puts dimensions into attachment url field" do
  419. object = %{
  420. "attachment" => [
  421. %{
  422. "type" => "Document",
  423. "name" => "Hello world",
  424. "url" => "https://media.example.tld/1.jpg",
  425. "width" => 880,
  426. "height" => 960,
  427. "mediaType" => "image/jpeg",
  428. "blurhash" => "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of"
  429. }
  430. ]
  431. }
  432. expected = %{
  433. "attachment" => [
  434. %{
  435. "type" => "Document",
  436. "name" => "Hello world",
  437. "url" => [
  438. %{
  439. "type" => "Link",
  440. "mediaType" => "image/jpeg",
  441. "href" => "https://media.example.tld/1.jpg",
  442. "width" => 880,
  443. "height" => 960
  444. }
  445. ],
  446. "mediaType" => "image/jpeg",
  447. "blurhash" => "eTKL26+HDjcEIBVl;ds+K6t301W.t7nit7y1E,R:v}ai4nXSt7V@of"
  448. }
  449. ]
  450. }
  451. assert Transmogrifier.fix_attachments(object) == expected
  452. end
  453. end
  454. describe "prepare_object/1" do
  455. test "it processes history" do
  456. original = %{
  457. "formerRepresentations" => %{
  458. "orderedItems" => [
  459. %{
  460. "generator" => %{},
  461. "emoji" => %{"blobcat" => "http://localhost:4001/emoji/blobcat.png"}
  462. }
  463. ]
  464. }
  465. }
  466. processed = Transmogrifier.prepare_object(original)
  467. history_item = Enum.at(processed["formerRepresentations"]["orderedItems"], 0)
  468. refute Map.has_key?(history_item, "generator")
  469. assert [%{"name" => ":blobcat:"}] = history_item["tag"]
  470. end
  471. test "it works when there is no or bad history" do
  472. original = %{
  473. "formerRepresentations" => %{
  474. "items" => [
  475. %{
  476. "generator" => %{},
  477. "emoji" => %{"blobcat" => "http://localhost:4001/emoji/blobcat.png"}
  478. }
  479. ]
  480. }
  481. }
  482. processed = Transmogrifier.prepare_object(original)
  483. assert processed["formerRepresentations"] == original["formerRepresentations"]
  484. end
  485. end
  486. end