diff options
author | Alexander Strizhakov <alex.strizhakov@gmail.com> | 2020-03-01 12:48:49 +0300 |
---|---|---|
committer | Alexander Strizhakov <alex.strizhakov@gmail.com> | 2020-03-01 12:48:49 +0300 |
commit | d9e4b77f8be8249b428a7ef1448c9a2161dee88a (patch) | |
tree | 927869f5bdadc70c579b2fcbe09223a9dd593ab1 /lib | |
parent | 814b275af7748df6bd11dfc6be1b4efce8d5ae70 (diff) | |
parent | 438394d40447bdfb590ff206ad80907294da0e65 (diff) | |
download | pleroma-d9e4b77f8be8249b428a7ef1448c9a2161dee88a.tar.gz |
Merge branch 'develop' into gun
Diffstat (limited to 'lib')
-rw-r--r-- | lib/pleroma/pagination.ex | 7 | ||||
-rw-r--r-- | lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex | 10 | ||||
-rw-r--r-- | lib/pleroma/plugs/rate_limiter/rate_limiter.ex | 177 | ||||
-rw-r--r-- | lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex | 11 |
4 files changed, 125 insertions, 80 deletions
diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index 4535ca7c5..43fb7babf 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Pagination do alias Pleroma.Repo @default_limit 20 + @max_limit 40 @page_keys ["max_id", "min_id", "limit", "since_id", "order"] def page_keys, do: @page_keys @@ -130,7 +131,11 @@ defmodule Pleroma.Pagination do end defp restrict(query, :limit, options, _table_binding) do - limit = Map.get(options, :limit, @default_limit) + limit = + case Map.get(options, :limit, @default_limit) do + limit when limit < @max_limit -> limit + _ -> @max_limit + end query |> limit(^limit) diff --git a/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex b/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex index 187582ede..884268d96 100644 --- a/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex +++ b/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex @@ -7,8 +7,8 @@ defmodule Pleroma.Plugs.RateLimiter.LimiterSupervisor do DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__) end - def add_limiter(limiter_name, expiration) do - {:ok, _pid} = + def add_or_return_limiter(limiter_name, expiration) do + result = DynamicSupervisor.start_child( __MODULE__, %{ @@ -28,6 +28,12 @@ defmodule Pleroma.Plugs.RateLimiter.LimiterSupervisor do ]} } ) + + case result do + {:ok, _pid} = result -> result + {:error, {:already_started, pid}} -> {:ok, pid} + _ -> result + end end @impl true diff --git a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex index 7fb92489c..b9cbe9716 100644 --- a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex +++ b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex @@ -7,12 +7,14 @@ defmodule Pleroma.Plugs.RateLimiter do ## 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 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`. @@ -64,91 +66,102 @@ defmodule Pleroma.Plugs.RateLimiter do import Pleroma.Web.TranslationHelpers import Plug.Conn + alias Pleroma.Config alias Pleroma.Plugs.RateLimiter.LimiterSupervisor alias Pleroma.User require Logger - def init(opts) do - limiter_name = Keyword.get(opts, :name) - - case Pleroma.Config.get([:rate_limit, limiter_name]) do - nil -> - nil - - config -> - name_root = Keyword.get(opts, :bucket_name, limiter_name) + @doc false + def init(plug_opts) do + plug_opts + end - %{ - name: name_root, - limits: config, - opts: opts - } + def call(conn, plug_opts) do + if disabled?() do + handle_disabled(conn) + else + action_settings = action_settings(plug_opts) + handle(conn, action_settings) end end - # Do not limit if there is no limiter configuration - def call(conn, nil), do: conn + defp handle_disabled(conn) do + if Config.get(:env) == :prod do + Logger.warn("Rate limiter is disabled for localhost/socket") + end + + conn + end - def call(conn, settings) do - case disabled?() do - true -> - if Pleroma.Config.get(:env) == :prod, - do: Logger.warn("Rate limiter is disabled for localhost/socket") + defp handle(conn, nil), do: conn + defp handle(conn, action_settings) do + action_settings + |> incorporate_conn_info(conn) + |> check_rate() + |> case do + {:ok, _count} -> conn - false -> - settings - |> incorporate_conn_info(conn) - |> check_rate() - |> case do - {:ok, _count} -> - conn - - {:error, _count} -> - render_throttled_error(conn) - end + {:error, _count} -> + render_throttled_error(conn) end end def disabled? do localhost_or_socket = - Pleroma.Config.get([Pleroma.Web.Endpoint, :http, :ip]) + Config.get([Pleroma.Web.Endpoint, :http, :ip]) |> Tuple.to_list() |> Enum.join(".") |> 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 end - def inspect_bucket(conn, name_root, settings) do - settings = - settings - |> incorporate_conn_info(conn) + @inspect_bucket_not_found {:error, :not_found} - bucket_name = make_bucket_name(%{settings | name: name_root}) - key_name = make_key_name(settings) - limit = get_limits(settings) + def inspect_bucket(conn, bucket_name_root, plug_opts) do + with %{name: _} = action_settings <- action_settings(plug_opts) do + 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 - {:error, :no_cache} -> - {:err, :not_found} + case Cachex.get(bucket_name, key_name) do + {:error, :no_cache} -> + @inspect_bucket_not_found - {:ok, nil} -> - {0, limit} + {:ok, nil} -> + {0, limit} - {:ok, value} -> - {value, limit - value} + {:ok, value} -> + {value, limit - value} + end + else + _ -> @inspect_bucket_not_found end end - defp check_rate(settings) do - bucket_name = make_bucket_name(settings) - key_name = make_key_name(settings) - limit = get_limits(settings) + def action_settings(plug_opts) do + with limiter_name when is_atom(limiter_name) <- plug_opts[:name], + limits when not is_nil(limits) <- Config.get([:rate_limit, limiter_name]) do + 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 {:commit, value} -> @@ -158,8 +171,8 @@ defmodule Pleroma.Plugs.RateLimiter do {:error, value} {:error, :no_cache} -> - initialize_buckets(settings) - check_rate(settings) + initialize_buckets!(action_settings) + check_rate(action_settings) end end @@ -169,16 +182,19 @@ defmodule Pleroma.Plugs.RateLimiter do defp increment_value(val, _limit), do: {:commit, val + 1} - defp incorporate_conn_info(settings, %{assigns: %{user: %User{id: user_id}}, params: params}) do - Map.merge(settings, %{ + defp incorporate_conn_info(action_settings, %{ + assigns: %{user: %User{id: user_id}}, + params: params + }) do + Map.merge(action_settings, %{ mode: :user, conn_params: params, conn_info: "#{user_id}" }) end - defp incorporate_conn_info(settings, %{params: params} = conn) do - Map.merge(settings, %{ + defp incorporate_conn_info(action_settings, %{params: params} = conn) do + Map.merge(action_settings, %{ mode: :anon, conn_params: params, conn_info: "#{ip(conn)}" @@ -197,10 +213,10 @@ defmodule Pleroma.Plugs.RateLimiter do |> halt() end - defp make_key_name(settings) do + defp make_key_name(action_settings) do "" - |> attach_params(settings) - |> attach_identity(settings) + |> attach_selected_params(action_settings) + |> attach_identity(action_settings) end defp get_scale(_, {scale, _}), do: scale @@ -215,28 +231,35 @@ defmodule Pleroma.Plugs.RateLimiter do defp get_limits(%{limits: [{_, limit}, _]}), do: limit - defp make_bucket_name(%{mode: :user, name: name_root}), - do: user_bucket_name(name_root) + defp make_bucket_name(%{mode: :user, name: bucket_name_root}), + do: user_bucket_name(bucket_name_root) - defp make_bucket_name(%{mode: :anon, name: name_root}), - do: anon_bucket_name(name_root) + defp make_bucket_name(%{mode: :anon, name: bucket_name_root}), + do: anon_bucket_name(bucket_name_root) - defp attach_params(input, %{conn_params: conn_params, opts: opts}) do - param_string = - opts + defp attach_selected_params(input, %{conn_params: conn_params, opts: plug_opts}) do + params_string = + plug_opts |> Keyword.get(:params, []) |> Enum.sort() |> Enum.map(&Map.get(conn_params, &1, "")) |> Enum.join(":") - "#{input}#{param_string}" + [input, params_string] + |> Enum.join(":") + |> String.replace_leading(":", "") end - defp initialize_buckets(%{name: _name, limits: nil}), do: :ok + defp initialize_buckets!(%{name: _name, limits: nil}), do: :ok + + defp initialize_buckets!(%{name: name, limits: limits}) do + {:ok, _pid} = + LimiterSupervisor.add_or_return_limiter(anon_bucket_name(name), get_scale(:anon, limits)) + + {:ok, _pid} = + LimiterSupervisor.add_or_return_limiter(user_bucket_name(name), get_scale(:user, limits)) - defp initialize_buckets(%{name: name, limits: limits}) do - LimiterSupervisor.add_limiter(anon_bucket_name(name), get_scale(:anon, limits)) - LimiterSupervisor.add_limiter(user_bucket_name(name), get_scale(:user, limits)) + :ok end defp attach_identity(base, %{mode: :user, conn_info: conn_info}), @@ -245,6 +268,6 @@ defmodule Pleroma.Plugs.RateLimiter do defp attach_identity(base, %{mode: :anon, conn_info: conn_info}), do: "ip:#{base}:#{conn_info}" - defp user_bucket_name(name_root), do: "user:#{name_root}" |> String.to_atom() - defp anon_bucket_name(name_root), do: "anon:#{name_root}" |> String.to_atom() + defp user_bucket_name(bucket_name_root), do: "user:#{bucket_name_root}" |> String.to_atom() + defp anon_bucket_name(bucket_name_root), do: "anon:#{bucket_name_root}" |> String.to_atom() end diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 29964a1d4..a3110c722 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -10,9 +10,20 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do alias Pleroma.Pagination alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Plugs.RateLimiter alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + # TODO: Replace with a macro when there is a Phoenix release with + # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e + # in it + + plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct) + plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public) + plug(RateLimiter, [name: :timeline, bucket_name: :home_timeline] when action == :home) + plug(RateLimiter, [name: :timeline, bucket_name: :hashtag_timeline] when action == :hashtag) + plug(RateLimiter, [name: :timeline, bucket_name: :list_timeline] when action == :list) + plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct]) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list) |