logo

pleroma

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

media_controller_test.exs (9797B)


  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.MastodonAPI.MediaControllerTest do
  5. use Pleroma.Web.ConnCase
  6. import ExUnit.CaptureLog
  7. import Mox
  8. alias Pleroma.Object
  9. alias Pleroma.UnstubbedConfigMock, as: ConfigMock
  10. alias Pleroma.User
  11. alias Pleroma.Web.ActivityPub.ActivityPub
  12. describe "Upload media" do
  13. setup do: oauth_access(["write:media"])
  14. setup do
  15. ConfigMock
  16. |> stub_with(Pleroma.Test.StaticConfig)
  17. image = %Plug.Upload{
  18. content_type: "image/jpeg",
  19. path: Path.absname("test/fixtures/image.jpg"),
  20. filename: "an_image.jpg"
  21. }
  22. [image: image]
  23. end
  24. setup do: clear_config([:media_proxy])
  25. setup do: clear_config([Pleroma.Upload])
  26. test "/api/v1/media", %{conn: conn, image: image} do
  27. desc = "Description of the image"
  28. media =
  29. conn
  30. |> put_req_header("content-type", "multipart/form-data")
  31. |> post("/api/v1/media", %{"file" => image, "description" => desc})
  32. |> json_response_and_validate_schema(:ok)
  33. assert media["type"] == "image"
  34. assert media["description"] == desc
  35. assert media["id"]
  36. object = Object.get_by_id(media["id"])
  37. assert object.data["actor"] == User.ap_id(conn.assigns[:user])
  38. end
  39. test "/api/v2/media", %{conn: conn, user: user, image: image} do
  40. desc = "Description of the image"
  41. response =
  42. conn
  43. |> put_req_header("content-type", "multipart/form-data")
  44. |> post("/api/v2/media", %{"file" => image, "description" => desc})
  45. |> json_response_and_validate_schema(200)
  46. assert media_id = response["id"]
  47. %{conn: conn} = oauth_access(["read:media"], user: user)
  48. media =
  49. conn
  50. |> get("/api/v1/media/#{media_id}")
  51. |> json_response_and_validate_schema(200)
  52. assert media["type"] == "image"
  53. assert media["description"] == desc
  54. assert media["id"]
  55. object = Object.get_by_id(media["id"])
  56. assert object.data["actor"] == user.ap_id
  57. end
  58. test "/api/v2/media, upload_limit", %{conn: conn, user: user} do
  59. clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
  60. desc = "Description of the binary"
  61. upload_limit = Config.get([:instance, :upload_limit]) * 8 + 8
  62. assert :ok ==
  63. File.write(Path.absname("test/tmp/large_binary.data"), <<0::size(upload_limit)>>)
  64. large_binary = %Plug.Upload{
  65. content_type: nil,
  66. path: Path.absname("test/tmp/large_binary.data"),
  67. filename: "large_binary.data"
  68. }
  69. assert capture_log(fn ->
  70. assert %{"error" => "file_too_large"} =
  71. conn
  72. |> put_req_header("content-type", "multipart/form-data")
  73. |> post("/api/v2/media", %{
  74. "file" => large_binary,
  75. "description" => desc
  76. })
  77. |> json_response_and_validate_schema(400)
  78. end) =~
  79. "[error] Elixir.Pleroma.Upload store (using Pleroma.Uploaders.Local) failed: :file_too_large"
  80. clear_config([:instance, :upload_limit], upload_limit)
  81. assert response =
  82. conn
  83. |> put_req_header("content-type", "multipart/form-data")
  84. |> post("/api/v2/media", %{
  85. "file" => large_binary,
  86. "description" => desc
  87. })
  88. |> json_response_and_validate_schema(200)
  89. assert media_id = response["id"]
  90. %{conn: conn} = oauth_access(["read:media"], user: user)
  91. media =
  92. conn
  93. |> get("/api/v1/media/#{media_id}")
  94. |> json_response_and_validate_schema(200)
  95. assert media["type"] == "unknown"
  96. assert media["description"] == desc
  97. assert media["id"]
  98. assert :ok == File.rm(Path.absname("test/tmp/large_binary.data"))
  99. end
  100. test "Do not allow nested filename", %{conn: conn, image: image} do
  101. image = %Plug.Upload{
  102. image
  103. | filename: "../../../../../nested/file.jpg"
  104. }
  105. desc = "Description of the image"
  106. media =
  107. conn
  108. |> put_req_header("content-type", "multipart/form-data")
  109. |> post("/api/v1/media", %{"file" => image, "description" => desc})
  110. |> json_response_and_validate_schema(:ok)
  111. refute Regex.match?(~r"/nested/", media["url"])
  112. end
  113. end
  114. describe "Update media description" do
  115. setup do: oauth_access(["write:media"])
  116. setup %{user: actor} do
  117. ConfigMock
  118. |> stub_with(Pleroma.Test.StaticConfig)
  119. file = %Plug.Upload{
  120. content_type: "image/jpeg",
  121. path: Path.absname("test/fixtures/image.jpg"),
  122. filename: "an_image.jpg"
  123. }
  124. {:ok, %Object{} = object} =
  125. ActivityPub.upload(
  126. file,
  127. actor: User.ap_id(actor),
  128. description: "test-m"
  129. )
  130. [object: object]
  131. end
  132. test "/api/v1/media/:id good request", %{conn: conn, object: object} do
  133. media =
  134. conn
  135. |> put_req_header("content-type", "multipart/form-data")
  136. |> put("/api/v1/media/#{object.id}", %{"description" => "test-media"})
  137. |> json_response_and_validate_schema(:ok)
  138. assert media["description"] == "test-media"
  139. assert refresh_record(object).data["name"] == "test-media"
  140. end
  141. end
  142. describe "Get media by id (/api/v1/media/:id)" do
  143. setup do: oauth_access(["read:media"])
  144. setup %{user: actor} do
  145. ConfigMock
  146. |> stub_with(Pleroma.Test.StaticConfig)
  147. file = %Plug.Upload{
  148. content_type: "image/jpeg",
  149. path: Path.absname("test/fixtures/image.jpg"),
  150. filename: "an_image.jpg"
  151. }
  152. {:ok, %Object{} = object} =
  153. ActivityPub.upload(
  154. file,
  155. actor: User.ap_id(actor),
  156. description: "test-media"
  157. )
  158. [object: object]
  159. end
  160. test "it returns media object when requested by owner", %{conn: conn, object: object} do
  161. media =
  162. conn
  163. |> get("/api/v1/media/#{object.id}")
  164. |> json_response_and_validate_schema(:ok)
  165. assert media["description"] == "test-media"
  166. assert media["type"] == "image"
  167. assert media["id"]
  168. end
  169. test "it returns 403 if media object requested by non-owner", %{object: object, user: user} do
  170. %{conn: conn, user: other_user} = oauth_access(["read:media"])
  171. assert object.data["actor"] == user.ap_id
  172. refute user.id == other_user.id
  173. conn
  174. |> get("/api/v1/media/#{object.id}")
  175. |> json_response_and_validate_schema(403)
  176. end
  177. end
  178. describe "Content-Type sanitization" do
  179. setup do: oauth_access(["write:media", "read:media"])
  180. setup do
  181. ConfigMock
  182. |> stub_with(Pleroma.Test.StaticConfig)
  183. config =
  184. Pleroma.Config.get([Pleroma.Upload])
  185. |> Keyword.put(:uploader, Pleroma.Uploaders.Local)
  186. clear_config([Pleroma.Upload], config)
  187. clear_config([Pleroma.Upload, :allowed_mime_types], ["image", "audio", "video"])
  188. # Create a file with a malicious content type and dangerous extension
  189. malicious_file = %Plug.Upload{
  190. content_type: "application/activity+json",
  191. path: Path.absname("test/fixtures/image.jpg"),
  192. # JSON extension to make MIME.from_path detect application/json
  193. filename: "malicious.json"
  194. }
  195. [malicious_file: malicious_file]
  196. end
  197. test "sanitizes malicious content types when serving media", %{
  198. conn: conn,
  199. malicious_file: malicious_file
  200. } do
  201. # First upload the file with the malicious content type
  202. media =
  203. conn
  204. |> put_req_header("content-type", "multipart/form-data")
  205. |> post("/api/v1/media", %{"file" => malicious_file})
  206. |> json_response_and_validate_schema(:ok)
  207. # Get the file URL from the response
  208. url = media["url"]
  209. # Now make a direct request to the media URL and check the content-type header
  210. response =
  211. build_conn()
  212. |> get(URI.parse(url).path)
  213. # Find the content-type header
  214. content_type_header =
  215. Enum.find(response.resp_headers, fn {name, _} -> name == "content-type" end)
  216. # The server should detect the application/json MIME type from the .json extension
  217. # and replace it with application/octet-stream since it's not in allowed_mime_types
  218. assert content_type_header == {"content-type", "application/octet-stream"}
  219. # Verify that the file was still served correctly
  220. assert response.status == 200
  221. end
  222. test "allows safe content types", %{conn: conn} do
  223. safe_image = %Plug.Upload{
  224. content_type: "image/jpeg",
  225. path: Path.absname("test/fixtures/image.jpg"),
  226. filename: "safe_image.jpg"
  227. }
  228. # Upload a file with a safe content type
  229. media =
  230. conn
  231. |> put_req_header("content-type", "multipart/form-data")
  232. |> post("/api/v1/media", %{"file" => safe_image})
  233. |> json_response_and_validate_schema(:ok)
  234. # Get the file URL from the response
  235. url = media["url"]
  236. # Make a direct request to the media URL and check the content-type header
  237. response =
  238. build_conn()
  239. |> get(URI.parse(url).path)
  240. # The server should preserve the image/jpeg MIME type since it's allowed
  241. content_type_header =
  242. Enum.find(response.resp_headers, fn {name, _} -> name == "content-type" end)
  243. assert content_type_header == {"content-type", "image/jpeg"}
  244. # Verify that the file was served correctly
  245. assert response.status == 200
  246. end
  247. end
  248. end