Merge branch 'feature/mention-mrf' into 'develop'
authorkaniini <ariadne@dereferenced.org>
Wed, 17 Jul 2019 15:28:41 +0000 (15:28 +0000)
committerkaniini <ariadne@dereferenced.org>
Wed, 17 Jul 2019 15:28:41 +0000 (15:28 +0000)
Add MRF MentionPolicy for dropping posts which mention specific actors

See merge request pleroma/pleroma!1439

16 files changed:
CHANGELOG.md
config/config.exs
config/test.exs
docs/config.md
lib/pleroma/upload/filter/dedupe.ex
lib/pleroma/upload/filter/mogrifun.ex
lib/pleroma/upload/filter/mogrify.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
test/upload/filter/dedupe_test.exs [new file with mode: 0644]
test/upload/filter/mogrifun_test.exs [new file with mode: 0644]
test/upload/filter/mogrify_test.exs [new file with mode: 0644]
test/upload/filter_test.exs [new file with mode: 0644]
test/web/mastodon_api/mastodon_api_controller_test.exs
test/web/twitter_api/twitter_api_controller_test.exs

index 0bc72a3fceb721020d7fc7d221039491dbfba9aa..0d91ad817874cfdef0e2aa5fe36fb760308936b7 100644 (file)
@@ -33,6 +33,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Add support for muting/unmuting notifications
 - Mastodon API: Add support for the `blocked_by` attribute in the relationship API (`GET /api/v1/accounts/relationships`). <https://github.com/tootsuite/mastodon/pull/10373>
 - Mastodon API: Add `pleroma.deactivated` to the Account entity
+- Mastodon API: added `/auth/password` endpoint for password reset with rate limit.
 - Admin API: Return users' tags when querying reports
 - Admin API: Return avatar and display name when querying users
 - Admin API: Allow querying user by ID
@@ -41,6 +42,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
 - Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
 - Addressable lists
+- Twitter API: added rate limit for `/api/account/password_reset` endpoint.
 
 ### Changed
 - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
index 7d539f994fdd43e596135b8064e539439ae9c960..38780eef7b2b0e872e76346dd8916dbbfad3942c 100644 (file)
@@ -528,8 +528,11 @@ config :http_signatures,
 config :pleroma, :rate_limit,
   search: [{1000, 10}, {1000, 30}],
   app_account_creation: {1_800_000, 25},
+  relations_actions: {10_000, 10},
+  relation_id_action: {60_000, 2},
   statuses_actions: {10_000, 15},
-  status_id_action: {60_000, 3}
+  status_id_action: {60_000, 3},
+  password_reset: {1_800_000, 5}
 
 # Import environment specific config. This must remain at the bottom
 # of this file so it overrides the configuration defined above.
index 96ecf3592447c5c4b55546b97ae2fcfd523ec86d..0af62aa1493fa1f0f6dda76cfb4d493bac5aad7a 100644 (file)
@@ -67,7 +67,8 @@ config :pleroma, Pleroma.ScheduledActivity,
 
 config :pleroma, :rate_limit,
   search: [{1000, 30}, {1000, 30}],
-  app_account_creation: {10_000, 5}
+  app_account_creation: {10_000, 5},
+  password_reset: {1000, 30}
 
 config :pleroma, :http_security, report_uri: "https://endpoint.com"
 
index 5f69f8daf2b1d613f2a3c726d700217244d12886..42556a0be2e73315b4c3b84f42c9f0d28f0b6156 100644 (file)
@@ -651,5 +651,7 @@ Supported rate limiters:
 
 * `:search` for the search requests (account & status search etc.)
 * `:app_account_creation` for registering user accounts from the same IP address
+* `:relations_actions` for actions on relations with all users (follow, unfollow)
+* `:relation_id_action` for actions on relation with a specific user (follow, unfollow)
 * `:statuses_actions` for create / delete / fav / unfav / reblog / unreblog actions on any statuses
 * `:status_id_action` for fav / unfav or reblog / unreblog actions on the same status by the same user
index e4c2258334e3a4aa754d7bbffd232df7f3be0feb..14928c355484165dd06f544fc4ce80a7bbf034c1 100644 (file)
@@ -6,10 +6,19 @@ defmodule Pleroma.Upload.Filter.Dedupe do
   @behaviour Pleroma.Upload.Filter
   alias Pleroma.Upload
 
-  def filter(%Upload{name: name} = upload) do
-    extension = String.split(name, ".") |> List.last()
-    shasum = :crypto.hash(:sha256, File.read!(upload.tempfile)) |> Base.encode16(case: :lower)
+  def filter(%Upload{name: name, tempfile: tempfile} = upload) do
+    extension =
+      name
+      |> String.split(".")
+      |> List.last()
+
+    shasum =
+      :crypto.hash(:sha256, File.read!(tempfile))
+      |> Base.encode16(case: :lower)
+
     filename = shasum <> "." <> extension
     {:ok, %Upload{upload | id: shasum, path: filename}}
   end
+
+  def filter(_), do: :ok
 end
index 35a5a13814807f1a787c1e136fd51aef3bf245d2..fee49fb5100590cc50f0e0c742e2506d05eafd64 100644 (file)
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Upload.Filter.Mogrifun do
   @behaviour Pleroma.Upload.Filter
+  alias Pleroma.Upload.Filter
 
   @filters [
     {"implode", "1"},
@@ -34,31 +35,10 @@ defmodule Pleroma.Upload.Filter.Mogrifun do
   ]
 
   def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
-    filter = Enum.random(@filters)
-
-    file
-    |> Mogrify.open()
-    |> mogrify_filter(filter)
-    |> Mogrify.save(in_place: true)
+    Filter.Mogrify.do_filter(file, [Enum.random(@filters)])
 
     :ok
   end
 
   def filter(_), do: :ok
-
-  defp mogrify_filter(mogrify, [filter | rest]) do
-    mogrify
-    |> mogrify_filter(filter)
-    |> mogrify_filter(rest)
-  end
-
-  defp mogrify_filter(mogrify, []), do: mogrify
-
-  defp mogrify_filter(mogrify, {action, options}) do
-    Mogrify.custom(mogrify, action, options)
-  end
-
-  defp mogrify_filter(mogrify, string) when is_binary(string) do
-    Mogrify.custom(mogrify, string)
-  end
 end
index f459eeecbf617645af0a48beca0f18ccdda8f897..91bfdd4f5bdf7157a8036ad9c8ecfe6c78d203f8 100644 (file)
@@ -11,16 +11,19 @@ defmodule Pleroma.Upload.Filter.Mogrify do
   def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
     filters = Pleroma.Config.get!([__MODULE__, :args])
 
+    do_filter(file, filters)
+    :ok
+  end
+
+  def filter(_), do: :ok
+
+  def do_filter(file, filters) do
     file
     |> Mogrify.open()
     |> mogrify_filter(filters)
     |> Mogrify.save(in_place: true)
-
-    :ok
   end
 
-  def filter(_), do: :ok
-
   defp mogrify_filter(mogrify, nil), do: mogrify
 
   defp mogrify_filter(mogrify, [filter | rest]) do
index f4aa576f73463482ccc35aea5c2bfa8acb9315c6..aff76e2eabf65d908786e36670848ce7d9113a81 100644 (file)
@@ -47,6 +47,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
 
   require Logger
 
+  @rate_limited_relations_actions ~w(follow unfollow)a
+
   @rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status
     post_status delete_status)a
 
@@ -62,9 +64,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     when action in ~w(fav_status unfav_status)a
   )
 
+  plug(
+    RateLimiter,
+    {:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions
+  )
+
+  plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions)
   plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
   plug(RateLimiter, :app_account_creation when action == :account_register)
   plug(RateLimiter, :search when action in [:search, :search2, :account_search])
+  plug(RateLimiter, :password_reset when action == :password_reset)
 
   @local_mastodon_name "Mastodon-Local"
 
@@ -1808,6 +1817,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  def password_reset(conn, params) do
+    nickname_or_email = params["email"] || params["nickname"]
+
+    with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
+      conn
+      |> put_status(:no_content)
+      |> json("")
+    else
+      {:error, "unknown user"} ->
+        put_status(conn, :not_found)
+
+      {:error, _} ->
+        put_status(conn, :bad_request)
+    end
+  end
+
   def try_render(conn, target, params)
       when is_binary(target) do
     case render(conn, target, params) do
index 3e5142e8a48b1aa054b983e1c6fdae63613334fd..52b8dc0bfb7232b630628c6fbe1856ca0c78a333 100644 (file)
@@ -691,6 +691,8 @@ defmodule Pleroma.Web.Router do
     get("/web/login", MastodonAPIController, :login)
     delete("/auth/sign_out", MastodonAPIController, :logout)
 
+    post("/auth/password", MastodonAPIController, :password_reset)
+
     scope [] do
       pipe_through(:oauth_read_or_public)
       get("/web/*path", MastodonAPIController, :index)
index 0313560a8d9493b5ce02efa88270a752b01c4ac6..8cb7035016170b85d772fa321d183d7bac5343b3 100644 (file)
@@ -27,6 +27,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
 
   require Logger
 
+  plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset)
   plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
   action_fallback(:errors)
 
@@ -437,6 +438,12 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
 
     with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
       json_response(conn, :no_content, "")
+    else
+      {:error, "unknown user"} ->
+        put_status(conn, :not_found)
+
+      {:error, _} ->
+        put_status(conn, :bad_request)
     end
   end
 
diff --git a/test/upload/filter/dedupe_test.exs b/test/upload/filter/dedupe_test.exs
new file mode 100644 (file)
index 0000000..fddd594
--- /dev/null
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.DedupeTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.Upload
+  alias Pleroma.Upload.Filter.Dedupe
+
+  @shasum "e30397b58d226d6583ab5b8b3c5defb0c682bda5c31ef07a9f57c1c4986e3781"
+
+  test "adds shasum" do
+    File.cp!(
+      "test/fixtures/image.jpg",
+      "test/fixtures/image_tmp.jpg"
+    )
+
+    upload = %Upload{
+      name: "an… image.jpg",
+      content_type: "image/jpg",
+      path: Path.absname("test/fixtures/image_tmp.jpg"),
+      tempfile: Path.absname("test/fixtures/image_tmp.jpg")
+    }
+
+    assert {
+             :ok,
+             %Pleroma.Upload{id: @shasum, path: "#{@shasum}.jpg"}
+           } = Dedupe.filter(upload)
+  end
+end
diff --git a/test/upload/filter/mogrifun_test.exs b/test/upload/filter/mogrifun_test.exs
new file mode 100644 (file)
index 0000000..d5a8751
--- /dev/null
@@ -0,0 +1,44 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.MogrifunTest do
+  use Pleroma.DataCase
+  import Mock
+
+  alias Pleroma.Upload
+  alias Pleroma.Upload.Filter
+
+  test "apply mogrify filter" do
+    File.cp!(
+      "test/fixtures/image.jpg",
+      "test/fixtures/image_tmp.jpg"
+    )
+
+    upload = %Upload{
+      name: "an… image.jpg",
+      content_type: "image/jpg",
+      path: Path.absname("test/fixtures/image_tmp.jpg"),
+      tempfile: Path.absname("test/fixtures/image_tmp.jpg")
+    }
+
+    task =
+      Task.async(fn ->
+        assert_receive {:apply_filter, {}}, 4_000
+      end)
+
+    with_mocks([
+      {Mogrify, [],
+       [
+         open: fn _f -> %Mogrify.Image{} end,
+         custom: fn _m, _a -> send(task.pid, {:apply_filter, {}}) end,
+         custom: fn _m, _a, _o -> send(task.pid, {:apply_filter, {}}) end,
+         save: fn _f, _o -> :ok end
+       ]}
+    ]) do
+      assert Filter.Mogrifun.filter(upload) == :ok
+    end
+
+    Task.await(task)
+  end
+end
diff --git a/test/upload/filter/mogrify_test.exs b/test/upload/filter/mogrify_test.exs
new file mode 100644 (file)
index 0000000..c301440
--- /dev/null
@@ -0,0 +1,51 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.MogrifyTest do
+  use Pleroma.DataCase
+  import Mock
+
+  alias Pleroma.Config
+  alias Pleroma.Upload
+  alias Pleroma.Upload.Filter
+
+  setup do
+    filter = Config.get([Filter.Mogrify, :args])
+
+    on_exit(fn ->
+      Config.put([Filter.Mogrify, :args], filter)
+    end)
+  end
+
+  test "apply mogrify filter" do
+    Config.put([Filter.Mogrify, :args], [{"tint", "40"}])
+
+    File.cp!(
+      "test/fixtures/image.jpg",
+      "test/fixtures/image_tmp.jpg"
+    )
+
+    upload = %Upload{
+      name: "an… image.jpg",
+      content_type: "image/jpg",
+      path: Path.absname("test/fixtures/image_tmp.jpg"),
+      tempfile: Path.absname("test/fixtures/image_tmp.jpg")
+    }
+
+    task =
+      Task.async(fn ->
+        assert_receive {:apply_filter, {_, "tint", "40"}}, 4_000
+      end)
+
+    with_mock Mogrify,
+      open: fn _f -> %Mogrify.Image{} end,
+      custom: fn _m, _a -> :ok end,
+      custom: fn m, a, o -> send(task.pid, {:apply_filter, {m, a, o}}) end,
+      save: fn _f, _o -> :ok end do
+      assert Filter.Mogrify.filter(upload) == :ok
+    end
+
+    Task.await(task)
+  end
+end
diff --git a/test/upload/filter_test.exs b/test/upload/filter_test.exs
new file mode 100644 (file)
index 0000000..640cd71
--- /dev/null
@@ -0,0 +1,39 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.FilterTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.Config
+  alias Pleroma.Upload.Filter
+
+  setup do
+    custom_filename = Config.get([Pleroma.Upload.Filter.AnonymizeFilename, :text])
+
+    on_exit(fn ->
+      Config.put([Pleroma.Upload.Filter.AnonymizeFilename, :text], custom_filename)
+    end)
+  end
+
+  test "applies filters" do
+    Config.put([Pleroma.Upload.Filter.AnonymizeFilename, :text], "custom-file.png")
+
+    File.cp!(
+      "test/fixtures/image.jpg",
+      "test/fixtures/image_tmp.jpg"
+    )
+
+    upload = %Pleroma.Upload{
+      name: "an… image.jpg",
+      content_type: "image/jpg",
+      path: Path.absname("test/fixtures/image_tmp.jpg"),
+      tempfile: Path.absname("test/fixtures/image_tmp.jpg")
+    }
+
+    assert Filter.filter([], upload) == {:ok, upload}
+
+    assert {:ok, upload} = Filter.filter([Pleroma.Upload.Filter.AnonymizeFilename], upload)
+    assert upload.name == "custom-file.png"
+  end
+end
index 85b4ad0240f25229d605e4990ef1771399153960..d9d8dafdbebafb352480f1e30faf37901b621c1a 100644 (file)
@@ -23,6 +23,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   import Pleroma.Factory
   import ExUnit.CaptureLog
   import Tesla.Mock
+  import Swoosh.TestAssertions
 
   @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
 
@@ -3807,4 +3808,55 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert Enum.empty?(response)
     end
   end
+
+  describe "POST /auth/password, with valid parameters" do
+    setup %{conn: conn} do
+      user = insert(:user)
+      conn = post(conn, "/auth/password?email=#{user.email}")
+      %{conn: conn, user: user}
+    end
+
+    test "it returns 204", %{conn: conn} do
+      assert json_response(conn, :no_content)
+    end
+
+    test "it creates a PasswordResetToken record for user", %{user: user} do
+      token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
+      assert token_record
+    end
+
+    test "it sends an email to user", %{user: user} do
+      token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
+
+      email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token)
+      notify_email = Pleroma.Config.get([:instance, :notify_email])
+      instance_name = Pleroma.Config.get([:instance, :name])
+
+      assert_email_sent(
+        from: {instance_name, notify_email},
+        to: {user.name, user.email},
+        html_body: email.html_body
+      )
+    end
+  end
+
+  describe "POST /auth/password, with invalid parameters" do
+    setup do
+      user = insert(:user)
+      {:ok, user: user}
+    end
+
+    test "it returns 404 when user is not found", %{conn: conn, user: user} do
+      conn = post(conn, "/auth/password?email=nonexisting_#{user.email}")
+      assert conn.status == 404
+      refute conn.resp_body
+    end
+
+    test "it returns 400 when user is not local", %{conn: conn, user: user} do
+      {:ok, user} = Repo.update(Changeset.change(user, local: false))
+      conn = post(conn, "/auth/password?email=#{user.email}")
+      assert conn.status == 400
+      refute conn.resp_body
+    end
+  end
 end
index de61775752cb9ffee95a171545411b652bfc1b29..622bf510e281a0f3c9b81d76cee73ef938874343 100644 (file)
@@ -1116,15 +1116,17 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
   describe "POST /api/account/password_reset, with invalid parameters" do
     setup [:valid_user]
 
-    test "it returns 500 when user is not found", %{conn: conn, user: user} do
+    test "it returns 404 when user is not found", %{conn: conn, user: user} do
       conn = post(conn, "/api/account/password_reset?email=nonexisting_#{user.email}")
-      assert json_response(conn, :internal_server_error)
+      assert conn.status == 404
+      refute conn.resp_body
     end
 
-    test "it returns 500 when user is not local", %{conn: conn, user: user} do
+    test "it returns 400 when user is not local", %{conn: conn, user: user} do
       {:ok, user} = Repo.update(Changeset.change(user, local: false))
       conn = post(conn, "/api/account/password_reset?email=#{user.email}")
-      assert json_response(conn, :internal_server_error)
+      assert conn.status == 400
+      refute conn.resp_body
     end
   end