Add back media proxy for resources pictures

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2021-01-26 10:05:01 +01:00
parent 7283831e08
commit a8e58547fd
8 changed files with 428 additions and 9 deletions

View File

@@ -0,0 +1,33 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mobilizon.Web.MediaProxyController do
use Mobilizon.Web, :controller
alias Mobilizon.Config
alias Mobilizon.Web.{MediaProxy, ReverseProxy}
alias Plug.Conn
# sobelow_skip ["XSS.SendResp"]
def remote(conn, %{"sig" => sig64, "url" => url64}) do
with {_, true} <- {:enabled, MediaProxy.enabled?()},
{:ok, url} <- MediaProxy.decode_url(sig64, url64),
:ok <- MediaProxy.verify_request_path_and_url(conn, url) do
ReverseProxy.call(conn, url, media_proxy_opts())
else
{:enabled, false} ->
send_resp(conn, 404, Conn.Status.reason_phrase(404))
{:error, :invalid_signature} ->
send_resp(conn, 403, Conn.Status.reason_phrase(403))
{:wrong_filename, filename} ->
redirect(conn, external: MediaProxy.build_url(sig64, url64, filename))
end
end
defp media_proxy_opts do
Config.get([:media_proxy, :proxy_opts], [])
end
end

115
lib/web/media_proxy.ex Normal file
View File

@@ -0,0 +1,115 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mobilizon.Web.MediaProxy do
@moduledoc """
Module to proxify remote media
"""
alias Mobilizon.Config
alias Mobilizon.Web
@base64_opts [padding: false]
def url(url) when is_nil(url) or url == "", do: nil
def url("/" <> _ = url), do: url
def url(url) do
if enabled?() and url_proxiable?(url) do
encode_url(url)
else
url
end
end
@spec url_proxiable?(String.t()) :: boolean()
def url_proxiable?(url) do
not local?(url)
end
def enabled?, do: Config.get([:media_proxy, :enabled], false)
# Note: media proxy must be enabled for media preview proxy in order to load all
# non-local non-whitelisted URLs through it and be sure that body size constraint is preserved.
def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :enabled])
def local?(url), do: String.starts_with?(url, Web.Endpoint.url())
defp base64_sig64(url) do
base64 = Base.url_encode64(url, @base64_opts)
sig64 =
base64
|> signed_url()
|> Base.url_encode64(@base64_opts)
{base64, sig64}
end
def encode_url(url) do
{base64, sig64} = base64_sig64(url)
build_url(sig64, base64, filename(url))
end
def decode_url(sig, url) do
with {:ok, sig} <- Base.url_decode64(sig, @base64_opts),
signature when signature == sig <- signed_url(url) do
{:ok, Base.url_decode64!(url, @base64_opts)}
else
_ -> {:error, :invalid_signature}
end
end
defp signed_url(url) do
:crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url)
end
def filename(url_or_path) do
if path = URI.parse(url_or_path).path, do: Path.basename(path)
end
def base_url do
Web.Endpoint.url()
end
defp proxy_url(path, sig_base64, url_base64, filename) do
[
base_url(),
path,
sig_base64,
url_base64,
filename
]
|> Enum.filter(& &1)
|> Path.join()
end
def build_url(sig_base64, url_base64, filename \\ nil) do
proxy_url("proxy", sig_base64, url_base64, filename)
end
def verify_request_path_and_url(
%Plug.Conn{params: %{"filename" => _}, request_path: request_path},
url
) do
verify_request_path_and_url(request_path, url)
end
def verify_request_path_and_url(request_path, url) when is_binary(request_path) do
filename = filename(url)
if filename && not basename_matches?(request_path, filename) do
{:wrong_filename, filename}
else
:ok
end
end
def verify_request_path_and_url(_, _), do: :ok
defp basename_matches?(path, filename) do
basename = Path.basename(path)
basename == filename or URI.decode(basename) == filename or URI.encode(basename) == filename
end
end

View File

@@ -4,17 +4,22 @@
# Upstream: https://git.pleroma.social/pleroma/pleroma/blob/develop/lib/pleroma/reverse_proxy.ex
defmodule Mobilizon.Web.ReverseProxy do
@keep_req_headers ~w(accept user-agent accept-encoding cache-control
if-modified-since if-unmodified-since if-none-match if-range range)
@resp_cache_headers ~w(etag date last-modified cache-control)
@keep_resp_headers @resp_cache_headers ++ ~w(content-type content-disposition
content-encoding content-range accept-ranges vary)
@range_headers ~w(range if-range)
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
~w(if-unmodified-since if-none-match) ++ @range_headers
@resp_cache_headers ~w(etag date last-modified)
@keep_resp_headers @resp_cache_headers ++
~w(content-length content-type content-disposition content-encoding) ++
~w(content-range accept-ranges vary)
@default_cache_control_header "public, max-age=1209600"
@valid_resp_codes [200, 206, 304]
@max_read_duration :timer.seconds(30)
@max_body_length :infinity
@methods ~w(GET HEAD)
def max_read_duration_default, do: @max_read_duration
def default_cache_control_header, do: @default_cache_control_header
@moduledoc """
A reverse proxy.

View File

@@ -163,6 +163,11 @@ defmodule Mobilizon.Web.Router do
post("/auth/:provider/callback", AuthController, :callback)
end
scope "/proxy/", Mobilizon.Web do
get("/:sig/:url", MediaProxyController, :remote)
get("/:sig/:url/:filename", MediaProxyController, :remote)
end
if Application.fetch_env!(:mobilizon, :env) in [:dev, :e2e] do
# If using Phoenix
forward("/sent_emails", Bamboo.SentEmailViewerPlug)