forked from AkkomaGang/akkoma
Merge branch 'rate-limiter-runtime-settings' into 'develop'
RateLimiter improvements: runtime configurability, no default limits in tests See merge request pleroma/pleroma!2250
This commit is contained in:
commit
19e559fe51
6 changed files with 218 additions and 184 deletions
|
@ -74,11 +74,7 @@
|
||||||
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"
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,32 +108,31 @@ def call(conn, settings) 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
|
@inspect_bucket_not_found {:error, :not_found}
|
||||||
settings =
|
|
||||||
settings
|
|
||||||
|> incorporate_conn_info(conn)
|
|
||||||
|
|
||||||
bucket_name = make_bucket_name(%{settings | name: name_root})
|
def inspect_bucket(conn, bucket_name_root, plug_opts) do
|
||||||
key_name = make_key_name(settings)
|
with %{name: _} = action_settings <- action_settings(plug_opts) do
|
||||||
limit = get_limits(settings)
|
action_settings = incorporate_conn_info(action_settings, conn)
|
||||||
|
bucket_name = make_bucket_name(%{action_settings | name: bucket_name_root})
|
||||||
|
key_name = make_key_name(action_settings)
|
||||||
|
limit = get_limits(action_settings)
|
||||||
|
|
||||||
case Cachex.get(bucket_name, key_name) do
|
case Cachex.get(bucket_name, key_name) do
|
||||||
{:error, :no_cache} ->
|
{:error, :no_cache} ->
|
||||||
{:err, :not_found}
|
@inspect_bucket_not_found
|
||||||
|
|
||||||
{:ok, nil} ->
|
{:ok, nil} ->
|
||||||
{0, limit}
|
{0, limit}
|
||||||
|
@ -143,12 +140,28 @@ def inspect_bucket(conn, name_root, settings) do
|
||||||
{:ok, value} ->
|
{:ok, value} ->
|
||||||
{value, limit - value}
|
{value, limit - value}
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
_ -> @inspect_bucket_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 is_atom(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 +171,8 @@ defp check_rate(settings) 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 +182,19 @@ defp increment_value(val, limit) when val >= limit, do: {:ignore, val}
|
||||||
|
|
||||||
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 +213,10 @@ defp render_throttled_error(conn) 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 +231,23 @@ defp get_limits(%{mode: :user, limits: [_, {_, limit}]}), do: limit
|
||||||
|
|
||||||
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 +263,6 @@ defp attach_identity(base, %{mode: :user, conn_info: conn_info}),
|
||||||
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
|
||||||
|
|
|
@ -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 @@ test "it restricts based on config values" 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 @@ test "it restricts based on config values" 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 {:error, :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 @@ test "are restricted based on remote IP" 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 @@ test "diffrerent users are counted independently" 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
|
||||||
|
|
|
@ -673,10 +673,48 @@ test "returns error when user already registred", %{conn: conn, valid_params: va
|
||||||
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 @@ test "rate limit", %{conn: conn} 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 @@ test "rate limit", %{conn: conn} 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
|
||||||
|
|
Loading…
Reference in a new issue