Merge remote-tracking branch 'pleroma/develop' into merge-pleroma

This commit is contained in:
Alex Gleason 2023-06-05 15:38:19 -05:00
commit 944fd73b36
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
21 changed files with 172 additions and 23 deletions

0
changelog.d/3831.skip Normal file
View file

1
changelog.d/3896.add Normal file
View file

@ -0,0 +1 @@
Validate Host header for MediaProxy and Uploads and return a 302 if the base_url has changed

1
changelog.d/3897.add Normal file
View file

@ -0,0 +1 @@
OnlyMedia Upload Filter

0
changelog.d/3899.skip Normal file
View file

View file

@ -672,6 +672,12 @@ This filter reads the ImageDescription and iptc:Caption-Abstract fields with Exi
No specific configuration.
#### Pleroma.Upload.Filter.OnlyMedia
This filter rejects uploads that are not identified with Content-Type matching audio/\*, image/\*, or video/\*
No specific configuration.
#### Pleroma.Upload.Filter.Mogrify
* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.

View file

@ -38,9 +38,9 @@ def filter([filter | rest], upload) do
{:ok, :noop} ->
filter(rest, upload)
error ->
Logger.error("#{__MODULE__}: Filter #{filter} failed: #{inspect(error)}")
error
{:error, e} ->
Logger.error("#{__MODULE__}: Filter #{filter} failed: #{inspect(e)}")
{:error, e}
end
end
end

View file

@ -0,0 +1,20 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.OnlyMedia do
@behaviour Pleroma.Upload.Filter
alias Pleroma.Upload
def filter(%Upload{content_type: content_type}) do
[type, _subtype] = String.split(content_type, "/")
if type in ["image", "video", "audio"] do
{:ok, :noop}
else
{:error, "Disallowed content-type: #{content_type}"}
end
end
def filter(_), do: {:ok, :noop}
end

View file

@ -12,6 +12,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
alias Pleroma.Web.MediaProxy
alias Plug.Conn
plug(:validate_host)
plug(:sandbox)
def remote(conn, %{"sig" => sig64, "url" => url64}) do
@ -205,6 +206,30 @@ defp media_proxy_opts do
Config.get([:media_proxy, :proxy_opts], [])
end
defp validate_host(conn, _params) do
%{scheme: proxy_scheme, host: proxy_host, port: proxy_port} =
MediaProxy.base_url() |> URI.parse()
if match?(^proxy_host, conn.host) do
conn
else
redirect_url =
%URI{
scheme: proxy_scheme,
host: proxy_host,
port: proxy_port,
path: conn.request_path,
query: conn.query_string
}
|> URI.to_string()
|> String.trim_trailing("?")
conn
|> Phoenix.Controller.redirect(external: redirect_url)
|> halt()
end
end
defp sandbox(conn, _params) do
conn
|> merge_resp_headers([{"content-security-policy", "sandbox;"}])

View file

@ -46,12 +46,32 @@ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
config = Pleroma.Config.get(Pleroma.Upload)
with uploader <- Keyword.fetch!(config, :uploader),
%{scheme: media_scheme, host: media_host, port: media_port} =
Pleroma.Upload.base_url() |> URI.parse()
with {:valid_host, true} <- {:valid_host, match?(^media_host, conn.host)},
uploader <- Keyword.fetch!(config, :uploader),
proxy_remote = Keyword.get(config, :proxy_remote, false),
{:ok, get_method} <- uploader.get_file(file),
false <- media_is_banned(conn, get_method) do
get_media(conn, get_method, proxy_remote, opts)
else
{:valid_host, false} ->
redirect_url =
%URI{
scheme: media_scheme,
host: media_host,
port: media_port,
path: conn.request_path,
query: conn.query_string
}
|> URI.to_string()
|> String.trim_trailing("?")
conn
|> Phoenix.Controller.redirect(external: redirect_url)
|> halt()
_ ->
conn
|> send_resp(:internal_server_error, dgettext("errors", "Failed"))

View file

@ -11,7 +11,7 @@ def project do
version: version("2.5.52"),
elixir: "~> 1.11",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
compilers: [:phoenix] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors()],
xref: [exclude: [:eldap]],
start_permanent: Mix.env() == :prod,
@ -132,10 +132,7 @@ defp deps do
{:telemetry_poller, "~> 1.0"},
# oban 2.14 requires Elixir 1.12+
{:oban, "~> 2.13.4"},
{:gettext,
git: "https://github.com/tusooa/gettext.git",
ref: "72fb2496b6c5280ed911bdc3756890e7f38a4808",
override: true},
{:gettext, "~> 0.20"},
{:bcrypt_elixir, "~> 2.2"},
{:trailing_format_plug, "~> 0.0.7"},
{:fast_sanitize, "~> 0.2.0"},

View file

@ -44,6 +44,7 @@
"ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"},
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"},
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
"fast_html": {:hex, :fast_html, "2.0.5", "c61760340606c1077ff1f196f17834056cb1dd3d5cb92a9f2cabf28bc6221c3c", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "605f4f4829443c14127694ebabb681778712ceecb4470ec32aa31012330e6506"},
"fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
@ -55,7 +56,7 @@
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
"geo": {:hex, :geo, "3.4.3", "0ddf3f681993d32c397e5ef346e7b4b6f36f39ed138502429832fa4000ebb9d5", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "e23f2892e5437ec8b063cee1beccec89c58fd841ae11133304700235feb25552"},
"geospatial": {:hex, :geospatial, "0.2.0", "c6c9f57df647cabbda71825bbba8465645002922a0c2e6410dc50279dbc95265", [:mix], [{:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: false]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:tesla, "~> 1.4.0", [hex: :tesla, repo: "hexpm", optional: false]}, {:tz_world, "~> 1.0", [hex: :tz_world, repo: "hexpm", optional: false]}], "hexpm", "b2f0e8f05a3d40f5473bf546d6b971bb82357e28c4f62c93c160d9e3c3581cb0"},
"gettext": {:git, "https://github.com/tusooa/gettext.git", "72fb2496b6c5280ed911bdc3756890e7f38a4808", [ref: "72fb2496b6c5280ed911bdc3756890e7f38a4808"]},
"gettext": {:hex, :gettext, "0.22.2", "6bfca374de34ecc913a28ba391ca184d88d77810a3e427afa8454a71a51341ac", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "8a2d389673aea82d7eae387e6a2ccc12660610080ae7beb19452cfdc1ec30f60"},
"glob": {:hex, :glob, "1.0.0", "b4d54d66e7797ce037cdd18f2587fc9932187355340e222cafe125cd333d7a0a", [:rebar3], [], "hexpm", "ca25de25ac5a762ba6c979718ae6afef8402cfc9155b87479d215fbe676801e1"},
"gun": {:hex, :gun, "2.0.0", "2326bc0fd6d9cf628419708270d6fe8b02b8d002cf992e4165a77d997b1defd0", [:make, :rebar3], [{:cowlib, "2.12.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "6613cb7c62930dc8d58263c44dda72f8556346ba88358fc929dcbc5f76d04569"},
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},

View file

@ -9,7 +9,6 @@
msgid ""
msgstr ""
"Language: en_test\n"
"Plural-Forms: nplurals=2\n"
#, elixir-format
#: lib/pleroma/web/api_spec/render_error.ex:122

View file

@ -9,7 +9,6 @@
msgid ""
msgstr ""
"Language: en_test\n"
"Plural-Forms: nplurals=2\n"
msgid "can't be blank"
msgstr ""

View file

@ -9,7 +9,6 @@
msgid ""
msgstr ""
"Language: en_test\n"
"Plural-Forms: nplurals=2\n"
msgid "eperm"
msgstr ""

View file

@ -21,10 +21,6 @@ msgstr ""
#~ ##
#~ ## Use "mix gettext.extract --merge" or "mix gettext.merge"
#~ ## to merge POT files into PO files.
#~ msgid ""
#~ msgstr ""
#~ "Language: en_test\n"
#~ "Plural-Forms: nplurals=2\n"
#, elixir-format
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9

View file

@ -9,7 +9,6 @@
msgid ""
msgstr ""
"Language: ru\n"
"Plural-Forms: nplurals=3\n"
msgid "can't be blank"
msgstr "не может быть пустым"

View file

@ -24,10 +24,6 @@ msgstr ""
##
## Use "mix gettext.extract --merge" or "mix gettext.merge"
## to merge POT files into PO files.
#~ msgid ""
#~ msgstr ""
#~ "Language: zh_Hans\n"
#~ "Plural-Forms: nplurals=1\n"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9
#, elixir-format

View file

@ -0,0 +1,32 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.OnlyMediaTest do
use Pleroma.DataCase, async: true
alias Pleroma.Upload
alias Pleroma.Upload.Filter.OnlyMedia
test "Allows media Content-Type" do
["audio/mpeg", "image/jpeg", "video/mp4"]
|> Enum.each(fn type ->
upload = %Upload{
content_type: type
}
assert {:ok, :noop} = OnlyMedia.filter(upload)
end)
end
test "Disallows non-media Content-Type" do
["application/javascript", "application/pdf", "text/html"]
|> Enum.each(fn type ->
upload = %Upload{
content_type: type
}
assert {:error, _} = OnlyMedia.filter(upload)
end)
end
end

View file

@ -54,6 +54,35 @@ test "it returns 403 for invalid signature", %{conn: conn, url: url} do
} = get(conn, "/proxy/hhgfh/eeee/fff")
end
test "it returns a 302 for invalid host", %{conn: conn} do
new_proxy_base = "http://mp.localhost/"
%{scheme: new_proxy_scheme, host: new_proxy_host, port: new_proxy_port} =
URI.parse(new_proxy_base)
clear_config([:media_proxy, :base_url], new_proxy_base)
proxy_url =
MediaProxy.encode_url("https://pleroma.social/logo.jpeg")
|> URI.parse()
|> Map.put(:host, "wronghost")
|> URI.to_string()
expected_url =
URI.parse(proxy_url)
|> Map.put(:host, new_proxy_host)
|> Map.put(:port, new_proxy_port)
|> Map.put(:scheme, new_proxy_scheme)
|> URI.to_string()
with_mock Pleroma.ReverseProxy,
call: fn _conn, _url, _opts -> %Conn{status: :success} end do
conn = get(conn, proxy_url)
assert redirected_to(conn, 302) == expected_url
end
end
test "redirects to valid url when filename is invalidated", %{conn: conn, url: url} do
invalid_url = String.replace(url, "test.png", "test-file.png")
response = get(conn, invalid_url)

View file

@ -40,4 +40,30 @@ test "sends Content-Disposition header when name param is set", %{
&(&1 == {"content-disposition", ~s[inline; filename="\\"cofe\\".gif"]})
)
end
test "denies access to media if wrong Host", %{
attachment_url: attachment_url
} do
conn = get(build_conn(), attachment_url)
assert conn.status == 200
new_media_base = "http://media.localhost:8080"
%{scheme: new_media_scheme, host: new_media_host, port: new_media_port} =
URI.parse(new_media_base)
clear_config([Pleroma.Upload, :base_url], new_media_base)
conn = get(build_conn(), attachment_url)
expected_url =
URI.parse(attachment_url)
|> Map.put(:host, new_media_host)
|> Map.put(:port, new_media_port)
|> Map.put(:scheme, new_media_scheme)
|> URI.to_string()
assert redirected_to(conn, 302) == expected_url
end
end

View file

@ -120,6 +120,9 @@ defp json_response_and_validate_schema(conn, _status) do
Mox.verify_on_exit!()
{:ok, conn: Phoenix.ConnTest.build_conn()}
{:ok,
conn:
Phoenix.ConnTest.build_conn()
|> Map.put(:host, Pleroma.Web.Endpoint.host())}
end
end