Runtime configurability of RateLimiter. Refactoring. Disabled default rate limits in tests.

This commit is contained in:
Ivan Tashkinov 2020-02-27 18:46:05 +03:00
parent fda6f35a46
commit 6f2efb1c45
6 changed files with 217 additions and 185 deletions

View File

@ -74,11 +74,7 @@ config :pleroma, Pleroma.ScheduledActivity,
total_user_limit: 3, total_user_limit: 3,
enabled: false enabled: false
config :pleroma, :rate_limit, config :pleroma, :rate_limit, %{}
search: [{1000, 30}, {1000, 30}],
app_account_creation: {10_000, 5},
password_reset: {1000, 30},
ap_routes: nil
config :pleroma, :http_security, report_uri: "https://endpoint.com" config :pleroma, :http_security, report_uri: "https://endpoint.com"

View File

@ -123,7 +123,7 @@ In addition to that, replace the existing nginx config's contents with the examp
If not an I2P-only instance, add the nginx config below to your existing config at `/etc/nginx/sites-enabled/pleroma.nginx`. If not an I2P-only instance, add the nginx config below to your existing config at `/etc/nginx/sites-enabled/pleroma.nginx`.
And for both cases, disable CSP in Pleroma's config (STS is disabled by default) so you can define those yourself seperately from the clearnet (if your instance is also on the clearnet). And for both cases, disable CSP in Pleroma's config (STS is disabled by default) so you can define those yourself separately from the clearnet (if your instance is also on the clearnet).
Copy the following into the `config/prod.secret.exs` in your Pleroma folder (/home/pleroma/pleroma/): Copy the following into the `config/prod.secret.exs` in your Pleroma folder (/home/pleroma/pleroma/):
``` ```
config :pleroma, :http_security, config :pleroma, :http_security,

View File

@ -75,7 +75,7 @@ If not a Tor-only instance,
add the nginx config below to your existing config at `/etc/nginx/sites-enabled/pleroma.nginx`. add the nginx config below to your existing config at `/etc/nginx/sites-enabled/pleroma.nginx`.
--- ---
For both cases, disable CSP in Pleroma's config (STS is disabled by default) so you can define those yourself seperately from the clearnet (if your instance is also on the clearnet). For both cases, disable CSP in Pleroma's config (STS is disabled by default) so you can define those yourself separately from the clearnet (if your instance is also on the clearnet).
Copy the following into the `config/prod.secret.exs` in your Pleroma folder (/home/pleroma/pleroma/): Copy the following into the `config/prod.secret.exs` in your Pleroma folder (/home/pleroma/pleroma/):
``` ```
config :pleroma, :http_security, config :pleroma, :http_security,

View File

@ -7,12 +7,14 @@ defmodule Pleroma.Plugs.RateLimiter do
## Configuration ## Configuration
A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where: A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration.
The basic configuration is a tuple where:
* The first element: `scale` (Integer). The time scale in milliseconds. * The first element: `scale` (Integer). The time scale in milliseconds.
* The second element: `limit` (Integer). How many requests to limit in the time scale provided. * The second element: `limit` (Integer). How many requests to limit in the time scale provided.
It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated. It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a
list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
To disable a limiter set its value to `nil`. To disable a limiter set its value to `nil`.
@ -64,42 +66,38 @@ defmodule Pleroma.Plugs.RateLimiter do
import Pleroma.Web.TranslationHelpers import Pleroma.Web.TranslationHelpers
import Plug.Conn import Plug.Conn
alias Pleroma.Config
alias Pleroma.Plugs.RateLimiter.LimiterSupervisor alias Pleroma.Plugs.RateLimiter.LimiterSupervisor
alias Pleroma.User alias Pleroma.User
require Logger require Logger
def init(opts) do @doc false
limiter_name = Keyword.get(opts, :name) def init(plug_opts) do
plug_opts
end
case Pleroma.Config.get([:rate_limit, limiter_name]) do def call(conn, plug_opts) do
nil -> if disabled?() do
nil handle_disabled(conn)
else
config -> action_settings = action_settings(plug_opts)
name_root = Keyword.get(opts, :bucket_name, limiter_name) handle(conn, action_settings)
%{
name: name_root,
limits: config,
opts: opts
}
end end
end end
# Do not limit if there is no limiter configuration defp handle_disabled(conn) do
def call(conn, nil), do: conn if Config.get(:env) == :prod do
Logger.warn("Rate limiter is disabled for localhost/socket")
def call(conn, settings) do end
case disabled?() do
true ->
if Pleroma.Config.get(:env) == :prod,
do: Logger.warn("Rate limiter is disabled for localhost/socket")
conn conn
end
false -> defp handle(conn, nil), do: conn
settings
defp handle(conn, action_settings) do
action_settings
|> incorporate_conn_info(conn) |> incorporate_conn_info(conn)
|> check_rate() |> check_rate()
|> case do |> case do
@ -110,28 +108,25 @@ defmodule Pleroma.Plugs.RateLimiter do
render_throttled_error(conn) render_throttled_error(conn)
end end
end end
end
def disabled? do def disabled? do
localhost_or_socket = localhost_or_socket =
Pleroma.Config.get([Pleroma.Web.Endpoint, :http, :ip]) Config.get([Pleroma.Web.Endpoint, :http, :ip])
|> Tuple.to_list() |> Tuple.to_list()
|> Enum.join(".") |> Enum.join(".")
|> String.match?(~r/^local|^127.0.0.1/) |> String.match?(~r/^local|^127.0.0.1/)
remote_ip_disabled = not Pleroma.Config.get([Pleroma.Plugs.RemoteIp, :enabled]) remote_ip_disabled = not Config.get([Pleroma.Plugs.RemoteIp, :enabled])
localhost_or_socket and remote_ip_disabled localhost_or_socket and remote_ip_disabled
end end
def inspect_bucket(conn, name_root, settings) do def inspect_bucket(conn, bucket_name_root, plug_opts) do
settings = with %{name: _} = action_settings <- action_settings(plug_opts) do
settings action_settings = incorporate_conn_info(action_settings, conn)
|> incorporate_conn_info(conn) bucket_name = make_bucket_name(%{action_settings | name: bucket_name_root})
key_name = make_key_name(action_settings)
bucket_name = make_bucket_name(%{settings | name: name_root}) limit = get_limits(action_settings)
key_name = make_key_name(settings)
limit = get_limits(settings)
case Cachex.get(bucket_name, key_name) do case Cachex.get(bucket_name, key_name) do
{:error, :no_cache} -> {:error, :no_cache} ->
@ -143,12 +138,28 @@ defmodule Pleroma.Plugs.RateLimiter do
{:ok, value} -> {:ok, value} ->
{value, limit - value} {value, limit - value}
end end
else
_ -> {:err, :not_found}
end
end end
defp check_rate(settings) do def action_settings(plug_opts) do
bucket_name = make_bucket_name(settings) with limiter_name when not is_nil(limiter_name) <- plug_opts[:name],
key_name = make_key_name(settings) limits when not is_nil(limits) <- Config.get([:rate_limit, limiter_name]) do
limit = get_limits(settings) bucket_name_root = Keyword.get(plug_opts, :bucket_name, limiter_name)
%{
name: bucket_name_root,
limits: limits,
opts: plug_opts
}
end
end
defp check_rate(action_settings) do
bucket_name = make_bucket_name(action_settings)
key_name = make_key_name(action_settings)
limit = get_limits(action_settings)
case Cachex.get_and_update(bucket_name, key_name, &increment_value(&1, limit)) do case Cachex.get_and_update(bucket_name, key_name, &increment_value(&1, limit)) do
{:commit, value} -> {:commit, value} ->
@ -158,8 +169,8 @@ defmodule Pleroma.Plugs.RateLimiter do
{:error, value} {:error, value}
{:error, :no_cache} -> {:error, :no_cache} ->
initialize_buckets(settings) initialize_buckets(action_settings)
check_rate(settings) check_rate(action_settings)
end end
end end
@ -169,16 +180,19 @@ defmodule Pleroma.Plugs.RateLimiter do
defp increment_value(val, _limit), do: {:commit, val + 1} defp increment_value(val, _limit), do: {:commit, val + 1}
defp incorporate_conn_info(settings, %{assigns: %{user: %User{id: user_id}}, params: params}) do defp incorporate_conn_info(action_settings, %{
Map.merge(settings, %{ assigns: %{user: %User{id: user_id}},
params: params
}) do
Map.merge(action_settings, %{
mode: :user, mode: :user,
conn_params: params, conn_params: params,
conn_info: "#{user_id}" conn_info: "#{user_id}"
}) })
end end
defp incorporate_conn_info(settings, %{params: params} = conn) do defp incorporate_conn_info(action_settings, %{params: params} = conn) do
Map.merge(settings, %{ Map.merge(action_settings, %{
mode: :anon, mode: :anon,
conn_params: params, conn_params: params,
conn_info: "#{ip(conn)}" conn_info: "#{ip(conn)}"
@ -197,10 +211,10 @@ defmodule Pleroma.Plugs.RateLimiter do
|> halt() |> halt()
end end
defp make_key_name(settings) do defp make_key_name(action_settings) do
"" ""
|> attach_params(settings) |> attach_selected_params(action_settings)
|> attach_identity(settings) |> attach_identity(action_settings)
end end
defp get_scale(_, {scale, _}), do: scale defp get_scale(_, {scale, _}), do: scale
@ -215,21 +229,23 @@ defmodule Pleroma.Plugs.RateLimiter do
defp get_limits(%{limits: [{_, limit}, _]}), do: limit defp get_limits(%{limits: [{_, limit}, _]}), do: limit
defp make_bucket_name(%{mode: :user, name: name_root}), defp make_bucket_name(%{mode: :user, name: bucket_name_root}),
do: user_bucket_name(name_root) do: user_bucket_name(bucket_name_root)
defp make_bucket_name(%{mode: :anon, name: name_root}), defp make_bucket_name(%{mode: :anon, name: bucket_name_root}),
do: anon_bucket_name(name_root) do: anon_bucket_name(bucket_name_root)
defp attach_params(input, %{conn_params: conn_params, opts: opts}) do defp attach_selected_params(input, %{conn_params: conn_params, opts: plug_opts}) do
param_string = params_string =
opts plug_opts
|> Keyword.get(:params, []) |> Keyword.get(:params, [])
|> Enum.sort() |> Enum.sort()
|> Enum.map(&Map.get(conn_params, &1, "")) |> Enum.map(&Map.get(conn_params, &1, ""))
|> Enum.join(":") |> Enum.join(":")
"#{input}#{param_string}" [input, params_string]
|> Enum.join(":")
|> String.replace_leading(":", "")
end end
defp initialize_buckets(%{name: _name, limits: nil}), do: :ok defp initialize_buckets(%{name: _name, limits: nil}), do: :ok
@ -245,6 +261,6 @@ defmodule Pleroma.Plugs.RateLimiter do
defp attach_identity(base, %{mode: :anon, conn_info: conn_info}), defp attach_identity(base, %{mode: :anon, conn_info: conn_info}),
do: "ip:#{base}:#{conn_info}" do: "ip:#{base}:#{conn_info}"
defp user_bucket_name(name_root), do: "user:#{name_root}" |> String.to_atom() defp user_bucket_name(bucket_name_root), do: "user:#{bucket_name_root}" |> String.to_atom()
defp anon_bucket_name(name_root), do: "anon:#{name_root}" |> String.to_atom() defp anon_bucket_name(bucket_name_root), do: "anon:#{bucket_name_root}" |> String.to_atom()
end end

View File

@ -6,69 +6,79 @@ defmodule Pleroma.Plugs.RateLimiterTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
use Plug.Test use Plug.Test
alias Pleroma.Config
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
import Pleroma.Factory import Pleroma.Factory
import Pleroma.Tests.Helpers, only: [clear_config: 1, clear_config: 2]
# Note: each example must work with separate buckets in order to prevent concurrency issues # Note: each example must work with separate buckets in order to prevent concurrency issues
clear_config([Pleroma.Web.Endpoint, :http, :ip])
clear_config(:rate_limit)
describe "config" do describe "config" do
@limiter_name :test_init
clear_config([Pleroma.Plugs.RemoteIp, :enabled])
test "config is required for plug to work" do test "config is required for plug to work" do
limiter_name = :test_init Config.put([:rate_limit, @limiter_name], {1, 1})
Pleroma.Config.put([:rate_limit, limiter_name], {1, 1}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
assert %{limits: {1, 1}, name: :test_init, opts: [name: :test_init]} == assert %{limits: {1, 1}, name: :test_init, opts: [name: :test_init]} ==
RateLimiter.init(name: limiter_name) [name: @limiter_name]
|> RateLimiter.init()
|> RateLimiter.action_settings()
assert nil == RateLimiter.init(name: :foo) assert nil ==
[name: :nonexisting_limiter]
|> RateLimiter.init()
|> RateLimiter.action_settings()
end end
test "it is disabled for localhost" do test "it is disabled for localhost" do
limiter_name = :test_init Config.put([:rate_limit, @limiter_name], {1, 1})
Pleroma.Config.put([:rate_limit, limiter_name], {1, 1}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1})
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1}) Config.put([Pleroma.Plugs.RemoteIp, :enabled], false)
Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], false)
assert RateLimiter.disabled?() == true assert RateLimiter.disabled?() == true
end end
test "it is disabled for socket" do test "it is disabled for socket" do
limiter_name = :test_init Config.put([:rate_limit, @limiter_name], {1, 1})
Pleroma.Config.put([:rate_limit, limiter_name], {1, 1}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"})
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"}) Config.put([Pleroma.Plugs.RemoteIp, :enabled], false)
Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], false)
assert RateLimiter.disabled?() == true assert RateLimiter.disabled?() == true
end end
test "it is enabled for socket when remote ip is enabled" do test "it is enabled for socket when remote ip is enabled" do
limiter_name = :test_init Config.put([:rate_limit, @limiter_name], {1, 1})
Pleroma.Config.put([:rate_limit, limiter_name], {1, 1}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"})
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"}) Config.put([Pleroma.Plugs.RemoteIp, :enabled], true)
Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], true)
assert RateLimiter.disabled?() == false assert RateLimiter.disabled?() == false
end end
test "it restricts based on config values" do test "it restricts based on config values" do
limiter_name = :test_opts limiter_name = :test_plug_opts
scale = 80 scale = 80
limit = 5 limit = 5
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit}) Config.put([:rate_limit, limiter_name], {scale, limit})
opts = RateLimiter.init(name: limiter_name) plug_opts = RateLimiter.init(name: limiter_name)
conn = conn(:get, "/") conn = conn(:get, "/")
for i <- 1..5 do for i <- 1..5 do
conn = RateLimiter.call(conn, opts) conn = RateLimiter.call(conn, plug_opts)
assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, opts) assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
Process.sleep(10) Process.sleep(10)
end end
conn = RateLimiter.call(conn, opts) conn = RateLimiter.call(conn, plug_opts)
assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
assert conn.halted assert conn.halted
@ -76,8 +86,8 @@ defmodule Pleroma.Plugs.RateLimiterTest do
conn = conn(:get, "/") conn = conn(:get, "/")
conn = RateLimiter.call(conn, opts) conn = RateLimiter.call(conn, plug_opts)
assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, opts) assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
refute conn.status == Plug.Conn.Status.code(:too_many_requests) refute conn.status == Plug.Conn.Status.code(:too_many_requests)
refute conn.resp_body refute conn.resp_body
@ -89,78 +99,81 @@ defmodule Pleroma.Plugs.RateLimiterTest do
test "`bucket_name` option overrides default bucket name" do test "`bucket_name` option overrides default bucket name" do
limiter_name = :test_bucket_name limiter_name = :test_bucket_name
Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5}) Config.put([:rate_limit, limiter_name], {1000, 5})
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
base_bucket_name = "#{limiter_name}:group1" base_bucket_name = "#{limiter_name}:group1"
opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name) plug_opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name)
conn = conn(:get, "/") conn = conn(:get, "/")
RateLimiter.call(conn, opts) RateLimiter.call(conn, plug_opts)
assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, opts) assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, plug_opts)
assert {:err, :not_found} = RateLimiter.inspect_bucket(conn, limiter_name, opts) assert {:err, :not_found} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
end end
test "`params` option allows different queries to be tracked independently" do test "`params` option allows different queries to be tracked independently" do
limiter_name = :test_params limiter_name = :test_params
Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5}) Config.put([:rate_limit, limiter_name], {1000, 5})
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
opts = RateLimiter.init(name: limiter_name, params: ["id"]) plug_opts = RateLimiter.init(name: limiter_name, params: ["id"])
conn = conn(:get, "/?id=1") conn = conn(:get, "/?id=1")
conn = Plug.Conn.fetch_query_params(conn) conn = Plug.Conn.fetch_query_params(conn)
conn_2 = conn(:get, "/?id=2") conn_2 = conn(:get, "/?id=2")
RateLimiter.call(conn, opts) RateLimiter.call(conn, plug_opts)
assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, opts) assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
assert {0, 5} = RateLimiter.inspect_bucket(conn_2, limiter_name, opts) assert {0, 5} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
end end
test "it supports combination of options modifying bucket name" do test "it supports combination of options modifying bucket name" do
limiter_name = :test_options_combo limiter_name = :test_options_combo
Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5}) Config.put([:rate_limit, limiter_name], {1000, 5})
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
base_bucket_name = "#{limiter_name}:group1" base_bucket_name = "#{limiter_name}:group1"
opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name, params: ["id"])
plug_opts =
RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name, params: ["id"])
id = "100" id = "100"
conn = conn(:get, "/?id=#{id}") conn = conn(:get, "/?id=#{id}")
conn = Plug.Conn.fetch_query_params(conn) conn = Plug.Conn.fetch_query_params(conn)
conn_2 = conn(:get, "/?id=#{101}") conn_2 = conn(:get, "/?id=#{101}")
RateLimiter.call(conn, opts) RateLimiter.call(conn, plug_opts)
assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, opts) assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, plug_opts)
assert {0, 5} = RateLimiter.inspect_bucket(conn_2, base_bucket_name, opts) assert {0, 5} = RateLimiter.inspect_bucket(conn_2, base_bucket_name, plug_opts)
end end
end end
describe "unauthenticated users" do describe "unauthenticated users" do
test "are restricted based on remote IP" do test "are restricted based on remote IP" do
limiter_name = :test_unauthenticated limiter_name = :test_unauthenticated
Pleroma.Config.put([:rate_limit, limiter_name], [{1000, 5}, {1, 10}]) Config.put([:rate_limit, limiter_name], [{1000, 5}, {1, 10}])
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
opts = RateLimiter.init(name: limiter_name) plug_opts = RateLimiter.init(name: limiter_name)
conn = %{conn(:get, "/") | remote_ip: {127, 0, 0, 2}} conn = %{conn(:get, "/") | remote_ip: {127, 0, 0, 2}}
conn_2 = %{conn(:get, "/") | remote_ip: {127, 0, 0, 3}} conn_2 = %{conn(:get, "/") | remote_ip: {127, 0, 0, 3}}
for i <- 1..5 do for i <- 1..5 do
conn = RateLimiter.call(conn, opts) conn = RateLimiter.call(conn, plug_opts)
assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, opts) assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
refute conn.halted refute conn.halted
end end
conn = RateLimiter.call(conn, opts) conn = RateLimiter.call(conn, plug_opts)
assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
assert conn.halted assert conn.halted
conn_2 = RateLimiter.call(conn_2, opts) conn_2 = RateLimiter.call(conn_2, plug_opts)
assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, opts) assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
refute conn_2.status == Plug.Conn.Status.code(:too_many_requests) refute conn_2.status == Plug.Conn.Status.code(:too_many_requests)
refute conn_2.resp_body refute conn_2.resp_body
@ -175,37 +188,37 @@ defmodule Pleroma.Plugs.RateLimiterTest do
:ok :ok
end end
test "can have limits seperate from unauthenticated connections" do test "can have limits separate from unauthenticated connections" do
limiter_name = :test_authenticated limiter_name = :test_authenticated1
scale = 50 scale = 50
limit = 5 limit = 5
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
Pleroma.Config.put([:rate_limit, limiter_name], [{1000, 1}, {scale, limit}]) Config.put([:rate_limit, limiter_name], [{1000, 1}, {scale, limit}])
opts = RateLimiter.init(name: limiter_name) plug_opts = RateLimiter.init(name: limiter_name)
user = insert(:user) user = insert(:user)
conn = conn(:get, "/") |> assign(:user, user) conn = conn(:get, "/") |> assign(:user, user)
for i <- 1..5 do for i <- 1..5 do
conn = RateLimiter.call(conn, opts) conn = RateLimiter.call(conn, plug_opts)
assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, opts) assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
refute conn.halted refute conn.halted
end end
conn = RateLimiter.call(conn, opts) conn = RateLimiter.call(conn, plug_opts)
assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
assert conn.halted assert conn.halted
end end
test "diffrerent users are counted independently" do test "different users are counted independently" do
limiter_name = :test_authenticated limiter_name = :test_authenticated2
Pleroma.Config.put([:rate_limit, limiter_name], [{1, 10}, {1000, 5}]) Config.put([:rate_limit, limiter_name], [{1, 10}, {1000, 5}])
Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
opts = RateLimiter.init(name: limiter_name) plug_opts = RateLimiter.init(name: limiter_name)
user = insert(:user) user = insert(:user)
conn = conn(:get, "/") |> assign(:user, user) conn = conn(:get, "/") |> assign(:user, user)
@ -214,16 +227,16 @@ defmodule Pleroma.Plugs.RateLimiterTest do
conn_2 = conn(:get, "/") |> assign(:user, user_2) conn_2 = conn(:get, "/") |> assign(:user, user_2)
for i <- 1..5 do for i <- 1..5 do
conn = RateLimiter.call(conn, opts) conn = RateLimiter.call(conn, plug_opts)
assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, opts) assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
end end
conn = RateLimiter.call(conn, opts) conn = RateLimiter.call(conn, plug_opts)
assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
assert conn.halted assert conn.halted
conn_2 = RateLimiter.call(conn_2, opts) conn_2 = RateLimiter.call(conn_2, plug_opts)
assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, opts) assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
refute conn_2.status == Plug.Conn.Status.code(:too_many_requests) refute conn_2.status == Plug.Conn.Status.code(:too_many_requests)
refute conn_2.resp_body refute conn_2.resp_body
refute conn_2.halted refute conn_2.halted

View File

@ -673,10 +673,48 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"} assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"}
end end
clear_config([Pleroma.Plugs.RemoteIp, :enabled]) test "returns bad_request if missing required params", %{
conn: conn,
valid_params: valid_params
} do
app_token = insert(:oauth_token, user: nil)
test "rate limit", %{conn: conn} do conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
res = post(conn, "/api/v1/accounts", valid_params)
assert json_response(res, 200)
[{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}]
|> Stream.zip(valid_params)
|> Enum.each(fn {ip, {attr, _}} ->
res =
conn
|> Map.put(:remote_ip, ip)
|> post("/api/v1/accounts", Map.delete(valid_params, attr))
|> json_response(400)
assert res == %{"error" => "Missing parameters"}
end)
end
test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
conn = put_req_header(conn, "authorization", "Bearer " <> "invalid-token")
res = post(conn, "/api/v1/accounts", valid_params)
assert json_response(res, 403) == %{"error" => "Invalid credentials"}
end
end
describe "create account by app / rate limit" do
clear_config([Pleroma.Plugs.RemoteIp, :enabled]) do
Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], true) Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], true)
end
clear_config([:rate_limit, :app_account_creation]) do
Pleroma.Config.put([:rate_limit, :app_account_creation], {10_000, 2})
end
test "respects rate limit setting", %{conn: conn} do
app_token = insert(:oauth_token, user: nil) app_token = insert(:oauth_token, user: nil)
conn = conn =
@ -684,7 +722,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|> put_req_header("authorization", "Bearer " <> app_token.token) |> put_req_header("authorization", "Bearer " <> app_token.token)
|> Map.put(:remote_ip, {15, 15, 15, 15}) |> Map.put(:remote_ip, {15, 15, 15, 15})
for i <- 1..5 do for i <- 1..2 do
conn = conn =
post(conn, "/api/v1/accounts", %{ post(conn, "/api/v1/accounts", %{
username: "#{i}lain", username: "#{i}lain",
@ -718,37 +756,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"} assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
end end
test "returns bad_request if missing required params", %{
conn: conn,
valid_params: valid_params
} do
app_token = insert(:oauth_token, user: nil)
conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
res = post(conn, "/api/v1/accounts", valid_params)
assert json_response(res, 200)
[{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}]
|> Stream.zip(valid_params)
|> Enum.each(fn {ip, {attr, _}} ->
res =
conn
|> Map.put(:remote_ip, ip)
|> post("/api/v1/accounts", Map.delete(valid_params, attr))
|> json_response(400)
assert res == %{"error" => "Missing parameters"}
end)
end
test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
conn = put_req_header(conn, "authorization", "Bearer " <> "invalid-token")
res = post(conn, "/api/v1/accounts", valid_params)
assert json_response(res, 403) == %{"error" => "Invalid credentials"}
end
end end
describe "GET /api/v1/accounts/:id/lists - account_lists" do describe "GET /api/v1/accounts/:id/lists - account_lists" do