Merge remote-tracking branch 'remotes/origin/develop' into feature/object-hashtags...
authorIvan Tashkinov <ivantashkinov@gmail.com>
Sun, 7 Mar 2021 08:34:39 +0000 (11:34 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Sun, 7 Mar 2021 08:34:39 +0000 (11:34 +0300)
CHANGELOG.md
lib/pleroma/reverse_proxy.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
mix.exs
mix.lock
priv/repo/migrations/20210121080964_add_default_text_search_config.exs
test/pleroma/reverse_proxy_test.exs
test/pleroma/web/mastodon_api/controllers/status_controller_test.exs

index 35f68d2fe25134c54b77379a043c012ac410b784..d01c8b5d4015e97e22fd76385581b75386a606d4 100644 (file)
@@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## Unreleased
 
+- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
+
+## Unreleased (Patch)
+
+## [2.3.0] - 2020-03-01
+
+### Security
+
+- Fixed client user agent leaking through MediaProxy
+
 ### Removed
 
 - `:auth, :enforce_oauth_admin_scope_usage` configuration option.
@@ -60,7 +70,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Ability to define custom HTTP headers per each frontend
 - MRF (`NoEmptyPolicy`): New MRF Policy which will deny empty statuses or statuses of only mentions from being created by local users
 - New users will receive a simple email confirming their registration if no other emails will be dispatched. (e.g., Welcome, Confirmation, or Approval Required)
-- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
 
 <details>
   <summary>API Changes</summary>
@@ -100,9 +109,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
   - Mastodon API: Support for expires_in/expires_at in the Filters.
 </details>
 
-## Unreleased (Patch)
-
-
 ## [2.2.2] - 2020-01-18
 
 ### Fixed
index 466906f035dff1bd714fb360d507816dab4f061d..406f7e2b878bc32d76675ccda8cde0c1a17618fd 100644 (file)
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.ReverseProxy do
   @range_headers ~w(range if-range)
-  @keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
+  @keep_req_headers ~w(accept 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 ++
@@ -57,9 +57,6 @@ defmodule Pleroma.ReverseProxy do
     * `false` will add `content-disposition: attachment` to any request,
     * a list of whitelisted content types
 
-    * `keep_user_agent` will forward the client's user-agent to the upstream. This may be useful if the upstream is
-    doing content transformation (encoding, …) depending on the request.
-
   * `req_headers`, `resp_headers` additional headers.
 
   * `http`: options for [hackney](https://github.com/benoitc/hackney) or [gun](https://github.com/ninenines/gun).
@@ -84,8 +81,7 @@ defmodule Pleroma.ReverseProxy do
   import Plug.Conn
 
   @type option() ::
-          {:keep_user_agent, boolean}
-          | {:max_read_duration, :timer.time() | :infinity}
+          {:max_read_duration, :timer.time() | :infinity}
           | {:max_body_length, non_neg_integer() | :infinity}
           | {:failed_request_ttl, :timer.time() | :infinity}
           | {:http, []}
@@ -291,17 +287,13 @@ defmodule Pleroma.ReverseProxy do
     end
   end
 
-  defp build_req_user_agent_header(headers, opts) do
-    if Keyword.get(opts, :keep_user_agent, false) do
-      List.keystore(
-        headers,
-        "user-agent",
-        0,
-        {"user-agent", Pleroma.Application.user_agent()}
-      )
-    else
-      headers
-    end
+  defp build_req_user_agent_header(headers, _opts) do
+    List.keystore(
+      headers,
+      "user-agent",
+      0,
+      {"user-agent", Pleroma.Application.user_agent()}
+    )
   end
 
   defp build_resp_headers(headers, opts) do
index 5b52b71dde5da6f7ab5a2746c1ac1894d908e2dd..d30c9fa68b444b99c9a433768727b81377442c07 100644 (file)
@@ -124,16 +124,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       ) do
     user = CommonAPI.get_user(activity.data["actor"])
     created_at = Utils.to_masto_date(activity.data["published"])
-    activity_object = Object.normalize(activity, fetch: false)
+    object = Object.normalize(activity, fetch: false)
 
     reblogged_parent_activity =
       if opts[:parent_activities] do
         Activity.Queries.find_by_object_ap_id(
           opts[:parent_activities],
-          activity_object.data["id"]
+          object.data["id"]
         )
       else
-        Activity.create_by_object_ap_id(activity_object.data["id"])
+        Activity.create_by_object_ap_id(object.data["id"])
         |> Activity.with_preloaded_bookmark(opts[:for])
         |> Activity.with_set_thread_muted_field(opts[:for])
         |> Repo.one()
@@ -142,7 +142,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
     reblog_rendering_opts = Map.put(opts, :activity, reblogged_parent_activity)
     reblogged = render("show.json", reblog_rendering_opts)
 
-    favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
+    favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
 
     bookmarked = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) != nil
 
@@ -154,8 +154,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
 
     %{
       id: to_string(activity.id),
-      uri: activity_object.data["id"],
-      url: activity_object.data["id"],
+      uri: object.data["id"],
+      url: object.data["id"],
       account:
         AccountView.render("show.json", %{
           user: user,
@@ -180,7 +180,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       media_attachments: reblogged[:media_attachments] || [],
       mentions: mentions,
       tags: reblogged[:tags] || [],
-      application: build_application(activity_object.data["generator"]),
+      application: build_application(object.data["generator"]),
       language: nil,
       emojis: [],
       pleroma: %{
@@ -538,6 +538,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   end
 
   @spec build_application(map() | nil) :: map() | nil
-  defp build_application(%{type: _type, name: name, url: url}), do: %{name: name, website: url}
+  defp build_application(%{"type" => _type, "name" => name, "url" => url}),
+    do: %{name: name, website: url}
+
   defp build_application(_), do: nil
 end
diff --git a/mix.exs b/mix.exs
index c06e27314eb15657f20dce5920c394d69d4fd4c2..87ee010734913468434a6666b45a394ca2de2b02 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
   def project do
     [
       app: :pleroma,
-      version: version("2.2.50"),
+      version: version("2.3.50"),
       elixir: "~> 1.9",
       elixirc_paths: elixirc_paths(Mix.env()),
       compilers: [:phoenix, :gettext] ++ Mix.compilers(),
@@ -158,7 +158,7 @@ defmodule Pleroma.Mixfile do
       {:floki, "~> 0.27"},
       {:timex, "~> 3.6"},
       {:ueberauth, "~> 0.4"},
-      {:linkify, "~> 0.4.1"},
+      {:linkify, "~> 0.5.0"},
       {:http_signatures, "~> 0.1.0"},
       {:telemetry, "~> 0.3"},
       {:poolboy, "~> 1.5"},
index cb09ffead4c2382247b98cfc187cc7280b4c028d..6d0635360368af4e15111049bef2255a32999766 100644 (file)
--- a/mix.lock
+++ b/mix.lock
@@ -66,7 +66,7 @@
   "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
   "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
   "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
-  "linkify": {:hex, :linkify, "0.4.1", "f881eb3429ae88010cf736e6fb3eed406c187bcdd544902ec937496636b7c7b3", [:mix], [], "hexpm", "ce98693f54ae9ace59f2f7a8aed3de2ef311381a8ce7794804bd75484c371dda"},
+  "linkify": {:hex, :linkify, "0.5.0", "e0ea8de73ff44742d6a889721221f4c4eccaad5284957ee9832ffeb347602d54", [:mix], [], "hexpm", "4ccd958350aee7c51c89e21f05b15d30596ebbba707e051d21766be1809df2d7"},
   "majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "289cda1b6d0d70ccb2ba508a2b0bd24638db2880", [ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"]},
   "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
   "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
index 09b6cccc9830006d68a5db65ae29f74ba666adc1..27f600b705551f709d2a8aea472d60903447c558 100644 (file)
@@ -4,7 +4,7 @@ defmodule Pleroma.Repo.Migrations.AddDefaultTextSearchConfig do
   def change do
     execute("DO $$
     BEGIN
-    execute 'ALTER DATABASE '||current_database()||' SET default_text_search_config = ''english'' ';
+    execute 'ALTER DATABASE \"'||current_database()||'\" SET default_text_search_config = ''english'' ';
     END
     $$;")
   end
index 499d29c06d53eba989d7506e93118a02d2fa8a98..a4dd8e99a5d6eb1d9a1629669a0a1440f06a6099 100644 (file)
@@ -18,24 +18,23 @@ defmodule Pleroma.ReverseProxyTest do
 
   setup :verify_on_exit!
 
-  defp user_agent_mock(user_agent, invokes) do
-    json = Jason.encode!(%{"user-agent": user_agent})
-
+  defp request_mock(invokes) do
     ClientMock
-    |> expect(:request, fn :get, url, _, _, _ ->
+    |> expect(:request, fn :get, url, headers, _body, _opts ->
       Registry.register(ClientMock, url, 0)
+      body = headers |> Enum.into(%{}) |> Jason.encode!()
 
       {:ok, 200,
        [
          {"content-type", "application/json"},
-         {"content-length", byte_size(json) |> to_string()}
-       ], %{url: url}}
+         {"content-length", byte_size(body) |> to_string()}
+       ], %{url: url, body: body}}
     end)
-    |> expect(:stream_body, invokes, fn %{url: url} = client ->
+    |> expect(:stream_body, invokes, fn %{url: url, body: body} = client ->
       case Registry.lookup(ClientMock, url) do
         [{_, 0}] ->
           Registry.update_value(ClientMock, url, &(&1 + 1))
-          {:ok, json, client}
+          {:ok, body, client}
 
         [{_, 1}] ->
           Registry.unregister(ClientMock, url)
@@ -46,7 +45,7 @@ defmodule Pleroma.ReverseProxyTest do
 
   describe "reverse proxy" do
     test "do not track successful request", %{conn: conn} do
-      user_agent_mock("hackney/1.15.1", 2)
+      request_mock(2)
       url = "/success"
 
       conn = ReverseProxy.call(conn, url)
@@ -56,18 +55,15 @@ defmodule Pleroma.ReverseProxyTest do
     end
   end
 
-  describe "user-agent" do
-    test "don't keep", %{conn: conn} do
-      user_agent_mock("hackney/1.15.1", 2)
-      conn = ReverseProxy.call(conn, "/user-agent")
-      assert json_response(conn, 200) == %{"user-agent" => "hackney/1.15.1"}
-    end
+  test "use Pleroma's user agent in the request; don't pass the client's", %{conn: conn} do
+    request_mock(2)
 
-    test "keep", %{conn: conn} do
-      user_agent_mock(Pleroma.Application.user_agent(), 2)
-      conn = ReverseProxy.call(conn, "/user-agent-keep", keep_user_agent: true)
-      assert json_response(conn, 200) == %{"user-agent" => Pleroma.Application.user_agent()}
-    end
+    conn =
+      conn
+      |> Plug.Conn.put_req_header("user-agent", "fake/1.0")
+      |> ReverseProxy.call("/user-agent")
+
+    assert json_response(conn, 200) == %{"user-agent" => Pleroma.Application.user_agent()}
   end
 
   test "closed connection", %{conn: conn} do
@@ -114,7 +110,7 @@ defmodule Pleroma.ReverseProxyTest do
 
   describe "max_body" do
     test "length returns error if content-length more than option", %{conn: conn} do
-      user_agent_mock("hackney/1.15.1", 0)
+      request_mock(0)
 
       assert capture_log(fn ->
                ReverseProxy.call(conn, "/huge-file", max_body_length: 4)
index bd385bccde6be6ea360c2aa68aaa0bd0879400b6..f616f405e39d4a029defa8f6de4ec7e06ba9557c 100644 (file)
@@ -376,6 +376,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
           "status" => "cofe is my copilot"
         })
 
+      assert %{
+               "content" => "cofe is my copilot"
+             } = json_response_and_validate_schema(result, 200)
+
+      activity = result.assigns.activity.id
+
+      result =
+        conn
+        |> get("api/v1/statuses/#{activity}")
+
       assert %{
                "content" => "cofe is my copilot",
                "application" => %{
@@ -396,6 +406,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
           "status" => "club mate is my wingman"
         })
 
+      assert %{"content" => "club mate is my wingman"} =
+               json_response_and_validate_schema(result, 200)
+
+      activity = result.assigns.activity.id
+
+      result =
+        conn
+        |> get("api/v1/statuses/#{activity}")
+
       assert %{
                "content" => "club mate is my wingman",
                "application" => nil