logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma

rate_limiter_test.exs (8695B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Plugs.RateLimiterTest do
  5. use Pleroma.Web.ConnCase
  6. alias Phoenix.ConnTest
  7. alias Pleroma.Config
  8. alias Pleroma.Plugs.RateLimiter
  9. alias Plug.Conn
  10. import Pleroma.Factory
  11. import Pleroma.Tests.Helpers, only: [clear_config: 1, clear_config: 2]
  12. # Note: each example must work with separate buckets in order to prevent concurrency issues
  13. setup do: clear_config([Pleroma.Web.Endpoint, :http, :ip])
  14. setup do: clear_config(:rate_limit)
  15. describe "config" do
  16. @limiter_name :test_init
  17. setup do: clear_config([Pleroma.Plugs.RemoteIp, :enabled])
  18. test "config is required for plug to work" do
  19. Config.put([:rate_limit, @limiter_name], {1, 1})
  20. Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
  21. assert %{limits: {1, 1}, name: :test_init, opts: [name: :test_init]} ==
  22. [name: @limiter_name]
  23. |> RateLimiter.init()
  24. |> RateLimiter.action_settings()
  25. assert nil ==
  26. [name: :nonexisting_limiter]
  27. |> RateLimiter.init()
  28. |> RateLimiter.action_settings()
  29. end
  30. end
  31. test "it is disabled if it remote ip plug is enabled but no remote ip is found" do
  32. assert RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, false))
  33. end
  34. test "it is enabled if remote ip found" do
  35. refute RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, true))
  36. end
  37. test "it is enabled if remote_ip_found flag doesn't exist" do
  38. refute RateLimiter.disabled?(build_conn())
  39. end
  40. test "it restricts based on config values" do
  41. limiter_name = :test_plug_opts
  42. scale = 80
  43. limit = 5
  44. Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
  45. Config.put([:rate_limit, limiter_name], {scale, limit})
  46. plug_opts = RateLimiter.init(name: limiter_name)
  47. conn = build_conn(:get, "/")
  48. for i <- 1..5 do
  49. conn = RateLimiter.call(conn, plug_opts)
  50. assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
  51. Process.sleep(10)
  52. end
  53. conn = RateLimiter.call(conn, plug_opts)
  54. assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
  55. assert conn.halted
  56. Process.sleep(50)
  57. conn = build_conn(:get, "/")
  58. conn = RateLimiter.call(conn, plug_opts)
  59. assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
  60. refute conn.status == Conn.Status.code(:too_many_requests)
  61. refute conn.resp_body
  62. refute conn.halted
  63. end
  64. describe "options" do
  65. test "`bucket_name` option overrides default bucket name" do
  66. limiter_name = :test_bucket_name
  67. Config.put([:rate_limit, limiter_name], {1000, 5})
  68. Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
  69. base_bucket_name = "#{limiter_name}:group1"
  70. plug_opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name)
  71. conn = build_conn(:get, "/")
  72. RateLimiter.call(conn, plug_opts)
  73. assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, plug_opts)
  74. assert {:error, :not_found} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
  75. end
  76. test "`params` option allows different queries to be tracked independently" do
  77. limiter_name = :test_params
  78. Config.put([:rate_limit, limiter_name], {1000, 5})
  79. Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
  80. plug_opts = RateLimiter.init(name: limiter_name, params: ["id"])
  81. conn = build_conn(:get, "/?id=1")
  82. conn = Conn.fetch_query_params(conn)
  83. conn_2 = build_conn(:get, "/?id=2")
  84. RateLimiter.call(conn, plug_opts)
  85. assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
  86. assert {0, 5} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
  87. end
  88. test "it supports combination of options modifying bucket name" do
  89. limiter_name = :test_options_combo
  90. Config.put([:rate_limit, limiter_name], {1000, 5})
  91. Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
  92. base_bucket_name = "#{limiter_name}:group1"
  93. plug_opts =
  94. RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name, params: ["id"])
  95. id = "100"
  96. conn = build_conn(:get, "/?id=#{id}")
  97. conn = Conn.fetch_query_params(conn)
  98. conn_2 = build_conn(:get, "/?id=#{101}")
  99. RateLimiter.call(conn, plug_opts)
  100. assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, plug_opts)
  101. assert {0, 5} = RateLimiter.inspect_bucket(conn_2, base_bucket_name, plug_opts)
  102. end
  103. end
  104. describe "unauthenticated users" do
  105. test "are restricted based on remote IP" do
  106. limiter_name = :test_unauthenticated
  107. Config.put([:rate_limit, limiter_name], [{1000, 5}, {1, 10}])
  108. Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
  109. plug_opts = RateLimiter.init(name: limiter_name)
  110. conn = %{build_conn(:get, "/") | remote_ip: {127, 0, 0, 2}}
  111. conn_2 = %{build_conn(:get, "/") | remote_ip: {127, 0, 0, 3}}
  112. for i <- 1..5 do
  113. conn = RateLimiter.call(conn, plug_opts)
  114. assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
  115. refute conn.halted
  116. end
  117. conn = RateLimiter.call(conn, plug_opts)
  118. assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
  119. assert conn.halted
  120. conn_2 = RateLimiter.call(conn_2, plug_opts)
  121. assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
  122. refute conn_2.status == Conn.Status.code(:too_many_requests)
  123. refute conn_2.resp_body
  124. refute conn_2.halted
  125. end
  126. end
  127. describe "authenticated users" do
  128. setup do
  129. Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
  130. :ok
  131. end
  132. test "can have limits separate from unauthenticated connections" do
  133. limiter_name = :test_authenticated1
  134. scale = 50
  135. limit = 5
  136. Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
  137. Config.put([:rate_limit, limiter_name], [{1000, 1}, {scale, limit}])
  138. plug_opts = RateLimiter.init(name: limiter_name)
  139. user = insert(:user)
  140. conn = build_conn(:get, "/") |> assign(:user, user)
  141. for i <- 1..5 do
  142. conn = RateLimiter.call(conn, plug_opts)
  143. assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
  144. refute conn.halted
  145. end
  146. conn = RateLimiter.call(conn, plug_opts)
  147. assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
  148. assert conn.halted
  149. end
  150. test "different users are counted independently" do
  151. limiter_name = :test_authenticated2
  152. Config.put([:rate_limit, limiter_name], [{1, 10}, {1000, 5}])
  153. Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
  154. plug_opts = RateLimiter.init(name: limiter_name)
  155. user = insert(:user)
  156. conn = build_conn(:get, "/") |> assign(:user, user)
  157. user_2 = insert(:user)
  158. conn_2 = build_conn(:get, "/") |> assign(:user, user_2)
  159. for i <- 1..5 do
  160. conn = RateLimiter.call(conn, plug_opts)
  161. assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
  162. end
  163. conn = RateLimiter.call(conn, plug_opts)
  164. assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
  165. assert conn.halted
  166. conn_2 = RateLimiter.call(conn_2, plug_opts)
  167. assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
  168. refute conn_2.status == Conn.Status.code(:too_many_requests)
  169. refute conn_2.resp_body
  170. refute conn_2.halted
  171. end
  172. end
  173. 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
  174. limiter_name = :test_race_condition
  175. Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5})
  176. Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
  177. opts = RateLimiter.init(name: limiter_name)
  178. conn = build_conn(:get, "/")
  179. conn_2 = build_conn(:get, "/")
  180. %Task{pid: pid1} =
  181. task1 =
  182. Task.async(fn ->
  183. receive do
  184. :process2_up ->
  185. RateLimiter.call(conn, opts)
  186. end
  187. end)
  188. task2 =
  189. Task.async(fn ->
  190. send(pid1, :process2_up)
  191. RateLimiter.call(conn_2, opts)
  192. end)
  193. Task.await(task1)
  194. Task.await(task2)
  195. refute {:err, :not_found} == RateLimiter.inspect_bucket(conn, limiter_name, opts)
  196. end
  197. end