diff options
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | config/config.exs | 5 | ||||
-rw-r--r-- | config/test.exs | 3 | ||||
-rw-r--r-- | docs/config.md | 2 | ||||
-rw-r--r-- | lib/pleroma/upload/filter/dedupe.ex | 15 | ||||
-rw-r--r-- | lib/pleroma/upload/filter/mogrifun.ex | 24 | ||||
-rw-r--r-- | lib/pleroma/upload/filter/mogrify.ex | 11 | ||||
-rw-r--r-- | lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 25 | ||||
-rw-r--r-- | lib/pleroma/web/router.ex | 2 | ||||
-rw-r--r-- | lib/pleroma/web/twitter_api/twitter_api_controller.ex | 7 | ||||
-rw-r--r-- | test/upload/filter/dedupe_test.exs | 31 | ||||
-rw-r--r-- | test/upload/filter/mogrifun_test.exs | 44 | ||||
-rw-r--r-- | test/upload/filter/mogrify_test.exs | 51 | ||||
-rw-r--r-- | test/upload/filter_test.exs | 39 | ||||
-rw-r--r-- | test/web/mastodon_api/mastodon_api_controller_test.exs | 52 | ||||
-rw-r--r-- | test/web/twitter_api/twitter_api_controller_test.exs | 10 |
16 files changed, 288 insertions, 35 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc72a3fc..0d91ad817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/config/config.exs b/config/config.exs index 7d539f994..38780eef7 100644 --- a/config/config.exs +++ b/config/config.exs @@ -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. diff --git a/config/test.exs b/config/test.exs index 96ecf3592..0af62aa14 100644 --- a/config/test.exs +++ b/config/test.exs @@ -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" diff --git a/docs/config.md b/docs/config.md index 5f69f8daf..42556a0be 100644 --- a/docs/config.md +++ b/docs/config.md @@ -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 diff --git a/lib/pleroma/upload/filter/dedupe.ex b/lib/pleroma/upload/filter/dedupe.ex index e4c225833..14928c355 100644 --- a/lib/pleroma/upload/filter/dedupe.ex +++ b/lib/pleroma/upload/filter/dedupe.ex @@ -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 diff --git a/lib/pleroma/upload/filter/mogrifun.ex b/lib/pleroma/upload/filter/mogrifun.ex index 35a5a1381..fee49fb51 100644 --- a/lib/pleroma/upload/filter/mogrifun.ex +++ b/lib/pleroma/upload/filter/mogrifun.ex @@ -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 diff --git a/lib/pleroma/upload/filter/mogrify.ex b/lib/pleroma/upload/filter/mogrify.ex index f459eeecb..91bfdd4f5 100644 --- a/lib/pleroma/upload/filter/mogrify.ex +++ b/lib/pleroma/upload/filter/mogrify.ex @@ -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 diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index f4aa576f7..aff76e2ea 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -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 diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 3e5142e8a..52b8dc0bf 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -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) diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 0313560a8..8cb703501 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -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 index 000000000..fddd594dc --- /dev/null +++ b/test/upload/filter/dedupe_test.exs @@ -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 index 000000000..d5a8751cc --- /dev/null +++ b/test/upload/filter/mogrifun_test.exs @@ -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 index 000000000..c301440fd --- /dev/null +++ b/test/upload/filter/mogrify_test.exs @@ -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 index 000000000..640cd7107 --- /dev/null +++ b/test/upload/filter_test.exs @@ -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 diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 85b4ad024..d9d8dafdb 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -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 diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs index de6177575..622bf510e 100644 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -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 |