X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=test%2Fplugs%2Frate_limiter_test.exs;h=8cdc8d1a2de0bdaca4848b4bc86b0809436fdede;hb=4d416343fae4a9e0b1654b12bd476017be63a7e9;hp=395095079f1346b377af6e251a2ebab54cb3fdea;hpb=f4c001062e51c207bf84f56473ddf6120e3b50fb;p=akkoma diff --git a/test/plugs/rate_limiter_test.exs b/test/plugs/rate_limiter_test.exs index 395095079..8cdc8d1a2 100644 --- a/test/plugs/rate_limiter_test.exs +++ b/test/plugs/rate_limiter_test.exs @@ -6,169 +6,271 @@ defmodule Pleroma.Plugs.RateLimiterTest do use ExUnit.Case, async: true use Plug.Test + alias Pleroma.Config alias Pleroma.Plugs.RateLimiter 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 - test "init/1" do - limiter_name = :test_init - Pleroma.Config.put([:rate_limit, limiter_name], {1, 1}) + clear_config([Pleroma.Web.Endpoint, :http, :ip]) + clear_config(:rate_limit) - assert {limiter_name, {1, 1}, []} == RateLimiter.init(limiter_name) - assert nil == RateLimiter.init(:foo) - end + describe "config" do + @limiter_name :test_init - test "ip/1" do - assert "127.0.0.1" == RateLimiter.ip(%{remote_ip: {127, 0, 0, 1}}) - end + clear_config([Pleroma.Plugs.RemoteIp, :enabled]) - test "it restricts by opts" do - limiter_name = :test_opts - scale = 1000 - limit = 5 + test "config is required for plug to work" do + Config.put([:rate_limit, @limiter_name], {1, 1}) + Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) - Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit}) + assert %{limits: {1, 1}, name: :test_init, opts: [name: :test_init]} == + [name: @limiter_name] + |> RateLimiter.init() + |> RateLimiter.action_settings() - opts = RateLimiter.init(limiter_name) - conn = conn(:get, "/") - bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}" + assert nil == + [name: :nonexisting_limiter] + |> RateLimiter.init() + |> RateLimiter.action_settings() + end - conn = RateLimiter.call(conn, opts) - assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + test "it is disabled for localhost" do + Config.put([:rate_limit, @limiter_name], {1, 1}) + Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1}) + Config.put([Pleroma.Plugs.RemoteIp, :enabled], false) - conn = RateLimiter.call(conn, opts) - assert {2, 3, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + assert RateLimiter.disabled?() == true + end - conn = RateLimiter.call(conn, opts) - assert {3, 2, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + test "it is disabled for socket" do + Config.put([:rate_limit, @limiter_name], {1, 1}) + Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"}) + Config.put([Pleroma.Plugs.RemoteIp, :enabled], false) - conn = RateLimiter.call(conn, opts) - assert {4, 1, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + assert RateLimiter.disabled?() == true + end - conn = RateLimiter.call(conn, opts) - assert {5, 0, to_reset, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + test "it is enabled for socket when remote ip is enabled" do + Config.put([:rate_limit, @limiter_name], {1, 1}) + Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"}) + Config.put([Pleroma.Plugs.RemoteIp, :enabled], true) - conn = RateLimiter.call(conn, opts) + assert RateLimiter.disabled?() == false + end - assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) - assert conn.halted + test "it restricts based on config values" do + limiter_name = :test_plug_opts + scale = 80 + limit = 5 - Process.sleep(to_reset) + Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) + Config.put([:rate_limit, limiter_name], {scale, limit}) - conn = conn(:get, "/") + plug_opts = RateLimiter.init(name: limiter_name) + conn = conn(:get, "/") - conn = RateLimiter.call(conn, opts) - assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + for i <- 1..5 do + conn = RateLimiter.call(conn, plug_opts) + assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) + Process.sleep(10) + end - refute conn.status == Plug.Conn.Status.code(:too_many_requests) - refute conn.resp_body - refute conn.halted - end + conn = RateLimiter.call(conn, plug_opts) + assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) + assert conn.halted - test "`bucket_name` option overrides default bucket name" do - limiter_name = :test_bucket_name - scale = 1000 - limit = 5 + Process.sleep(50) - Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit}) - base_bucket_name = "#{limiter_name}:group1" - opts = RateLimiter.init({limiter_name, bucket_name: base_bucket_name}) + conn = conn(:get, "/") - conn = conn(:get, "/") - default_bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}" - customized_bucket_name = "#{base_bucket_name}:#{RateLimiter.ip(conn)}" + conn = RateLimiter.call(conn, plug_opts) + assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) - RateLimiter.call(conn, opts) - assert {1, 4, _, _, _} = ExRated.inspect_bucket(customized_bucket_name, scale, limit) - assert {0, 5, _, _, _} = ExRated.inspect_bucket(default_bucket_name, scale, limit) + refute conn.status == Plug.Conn.Status.code(:too_many_requests) + refute conn.resp_body + refute conn.halted + end end - test "`params` option appends specified params' values to bucket name" do - limiter_name = :test_params - scale = 1000 - limit = 5 + describe "options" do + test "`bucket_name` option overrides default bucket name" do + limiter_name = :test_bucket_name + + Config.put([:rate_limit, limiter_name], {1000, 5}) + Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) + + base_bucket_name = "#{limiter_name}:group1" + plug_opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name) + + conn = conn(:get, "/") + + RateLimiter.call(conn, plug_opts) + assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, plug_opts) + assert {:error, :not_found} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) + end + + test "`params` option allows different queries to be tracked independently" do + limiter_name = :test_params + Config.put([:rate_limit, limiter_name], {1000, 5}) + Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) + + plug_opts = RateLimiter.init(name: limiter_name, params: ["id"]) + + conn = conn(:get, "/?id=1") + conn = Plug.Conn.fetch_query_params(conn) + conn_2 = conn(:get, "/?id=2") - Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit}) - opts = RateLimiter.init({limiter_name, params: ["id"]}) - id = "1" + RateLimiter.call(conn, plug_opts) + assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) + assert {0, 5} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts) + end - conn = conn(:get, "/?id=#{id}") - conn = Plug.Conn.fetch_query_params(conn) + test "it supports combination of options modifying bucket name" do + limiter_name = :test_options_combo + Config.put([:rate_limit, limiter_name], {1000, 5}) + Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) - default_bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}" - parametrized_bucket_name = "#{limiter_name}:#{id}:#{RateLimiter.ip(conn)}" + base_bucket_name = "#{limiter_name}:group1" - RateLimiter.call(conn, opts) - assert {1, 4, _, _, _} = ExRated.inspect_bucket(parametrized_bucket_name, scale, limit) - assert {0, 5, _, _, _} = ExRated.inspect_bucket(default_bucket_name, scale, limit) + plug_opts = + RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name, params: ["id"]) + + id = "100" + + conn = conn(:get, "/?id=#{id}") + conn = Plug.Conn.fetch_query_params(conn) + conn_2 = conn(:get, "/?id=#{101}") + + RateLimiter.call(conn, plug_opts) + assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, plug_opts) + assert {0, 5} = RateLimiter.inspect_bucket(conn_2, base_bucket_name, plug_opts) + end end - test "it supports combination of options modifying bucket name" do - limiter_name = :test_options_combo - scale = 1000 - limit = 5 + describe "unauthenticated users" do + test "are restricted based on remote IP" do + limiter_name = :test_unauthenticated + Config.put([:rate_limit, limiter_name], [{1000, 5}, {1, 10}]) + Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) + + plug_opts = RateLimiter.init(name: limiter_name) + + conn = %{conn(:get, "/") | remote_ip: {127, 0, 0, 2}} + conn_2 = %{conn(:get, "/") | remote_ip: {127, 0, 0, 3}} + + for i <- 1..5 do + conn = RateLimiter.call(conn, plug_opts) + assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) + refute conn.halted + end - Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit}) - base_bucket_name = "#{limiter_name}:group1" - opts = RateLimiter.init({limiter_name, bucket_name: base_bucket_name, params: ["id"]}) - id = "100" + conn = RateLimiter.call(conn, plug_opts) - conn = conn(:get, "/?id=#{id}") - conn = Plug.Conn.fetch_query_params(conn) + assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) + assert conn.halted - default_bucket_name = "#{limiter_name}:#{RateLimiter.ip(conn)}" - parametrized_bucket_name = "#{base_bucket_name}:#{id}:#{RateLimiter.ip(conn)}" + conn_2 = RateLimiter.call(conn_2, plug_opts) + assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts) - RateLimiter.call(conn, opts) - assert {1, 4, _, _, _} = ExRated.inspect_bucket(parametrized_bucket_name, scale, limit) - assert {0, 5, _, _, _} = ExRated.inspect_bucket(default_bucket_name, scale, limit) + refute conn_2.status == Plug.Conn.Status.code(:too_many_requests) + refute conn_2.resp_body + refute conn_2.halted + end end - test "optional limits for authenticated users" do - limiter_name = :test_authenticated - Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo) + describe "authenticated users" do + setup do + Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo) - scale = 1000 - limit = 5 - Pleroma.Config.put([:rate_limit, limiter_name], [{1, 10}, {scale, limit}]) + :ok + end - opts = RateLimiter.init(limiter_name) + test "can have limits separate from unauthenticated connections" do + limiter_name = :test_authenticated1 - user = insert(:user) - conn = conn(:get, "/") |> assign(:user, user) - bucket_name = "#{limiter_name}:#{user.id}" + scale = 50 + limit = 5 + Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) + Config.put([:rate_limit, limiter_name], [{1000, 1}, {scale, limit}]) - conn = RateLimiter.call(conn, opts) - assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + plug_opts = RateLimiter.init(name: limiter_name) - conn = RateLimiter.call(conn, opts) - assert {2, 3, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + user = insert(:user) + conn = conn(:get, "/") |> assign(:user, user) - conn = RateLimiter.call(conn, opts) - assert {3, 2, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + for i <- 1..5 do + conn = RateLimiter.call(conn, plug_opts) + assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) + refute conn.halted + end - conn = RateLimiter.call(conn, opts) - assert {4, 1, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + conn = RateLimiter.call(conn, plug_opts) - conn = RateLimiter.call(conn, opts) - assert {5, 0, to_reset, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) + assert conn.halted + end - conn = RateLimiter.call(conn, opts) + test "different users are counted independently" do + limiter_name = :test_authenticated2 + Config.put([:rate_limit, limiter_name], [{1, 10}, {1000, 5}]) + Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) - assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) - assert conn.halted + plug_opts = RateLimiter.init(name: limiter_name) - Process.sleep(to_reset) + user = insert(:user) + conn = conn(:get, "/") |> assign(:user, user) - conn = conn(:get, "/") |> assign(:user, user) + user_2 = insert(:user) + conn_2 = conn(:get, "/") |> assign(:user, user_2) - conn = RateLimiter.call(conn, opts) - assert {1, 4, _, _, _} = ExRated.inspect_bucket(bucket_name, scale, limit) + for i <- 1..5 do + conn = RateLimiter.call(conn, plug_opts) + assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts) + end - refute conn.status == Plug.Conn.Status.code(:too_many_requests) - refute conn.resp_body - refute conn.halted + conn = RateLimiter.call(conn, plug_opts) + assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) + assert conn.halted + + conn_2 = RateLimiter.call(conn_2, plug_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.resp_body + refute conn_2.halted + end + end + + test "doesn't crash due to a race condition when multiple requests are made at the same time and the bucket is not yet initialized" do + limiter_name = :test_race_condition + Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5}) + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) + + opts = RateLimiter.init(name: limiter_name) + + conn = conn(:get, "/") + conn_2 = conn(:get, "/") + + %Task{pid: pid1} = + task1 = + Task.async(fn -> + receive do + :process2_up -> + RateLimiter.call(conn, opts) + end + end) + + task2 = + Task.async(fn -> + send(pid1, :process2_up) + RateLimiter.call(conn_2, opts) + end) + + Task.await(task1) + Task.await(task2) + + refute {:err, :not_found} == RateLimiter.inspect_bucket(conn, limiter_name, opts) end end