From: rinpatch Date: Thu, 3 Jan 2019 17:04:27 +0000 (+0000) Subject: Merge branch 'captcha' into 'develop' X-Git-Url: http://git.squeep.com/?a=commitdiff_plain;h=b73a1a33de76dc848037a5d0e951866bd21f92c4;hp=-c;p=akkoma Merge branch 'captcha' into 'develop' Make captcha (kocaptcha) stateless See merge request pleroma/pleroma!585 --- b73a1a33de76dc848037a5d0e951866bd21f92c4 diff --combined config/config.exs index 11ee220e7,9cb81cf47..34b516e02 --- a/config/config.exs +++ b/config/config.exs @@@ -12,7 -12,7 +12,7 @@@ config :pleroma, Pleroma.Repo, types: P config :pleroma, Pleroma.Captcha, enabled: false, - seconds_retained: 180, + seconds_valid: 60, method: Pleroma.Captcha.Kocaptcha config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch" @@@ -54,17 -54,6 +54,17 @@@ config :pleroma, :uri_schemes "xmpp" ] +websocket_config = [ + path: "/websocket", + serializer: [ + {Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"}, + {Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"} + ], + timeout: 60_000, + transport_log: false, + compress: false +] + # Configures the endpoint config :pleroma, Pleroma.Web.Endpoint, url: [host: "localhost"], @@@ -73,8 -62,6 +73,8 @@@ {:_, [ {"/api/v1/streaming", Elixir.Pleroma.Web.MastodonAPI.WebsocketHandler, []}, + {"/socket/websocket", Phoenix.Endpoint.CowboyWebSocket, + {nil, {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}}, {:_, Plug.Adapters.Cowboy.Handler, {Pleroma.Web.Endpoint, []}} ]} ] @@@ -111,8 -98,7 +111,8 @@@ config :pleroma, :instance name: "Pleroma", email: "example@example.com", description: "A Pleroma instance, an alternative fediverse server", - limit: 5000, + limit: 5_000, + remote_limit: 100_000, upload_limit: 16_000_000, avatar_upload_limit: 2_000_000, background_upload_limit: 4_000_000, @@@ -151,8 -137,8 +151,8 @@@ config :pleroma, :fe logo_mask: true, logo_margin: "0.1em", background: "/static/aurora_borealis.jpg", - redirect_root_no_login: "/~/main/all", - redirect_root_login: "/~/main/friends", + redirect_root_no_login: "/main/all", + redirect_root_login: "/main/friends", show_instance_panel: true, scope_options_enabled: false, formatting_options_enabled: false, @@@ -177,8 -163,6 +177,8 @@@ config :pleroma, :mrf_rejectnonpublic allow_followersonly: false, allow_direct: false +config :pleroma, :mrf_hellthread, threshold: 10 + config :pleroma, :mrf_simple, media_removal: [], media_nsfw: [], @@@ -234,46 -218,6 +234,46 @@@ config :cors_plug credentials: true, headers: ["Authorization", "Content-Type", "Idempotency-Key"] +config :pleroma, Pleroma.User, + restricted_nicknames: [ + "about", + "~", + "main", + "users", + "settings", + "objects", + "activities", + "web", + "registration", + "friend-requests", + "pleroma", + "api", + "tag", + "notice", + "status", + "user-search", + "ostatus_subscribe", + "oauth", + "push", + "relay", + "inbox", + ".well-known", + "nodeinfo", + "auth", + "proxy", + "dev", + "internal", + "media" + ] + +config :pleroma, Pleroma.Web.Federator, max_jobs: 50 + +config :pleroma, Pleroma.Web.Federator.RetryQueue, + enabled: false, + max_jobs: 20, + initial_timeout: 30, + max_retries: 5 + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --combined docs/config.md index f4bcae3fd,cfeca8eb4..1c3219efe --- a/docs/config.md +++ b/docs/config.md @@@ -63,7 -63,6 +63,7 @@@ config :pleroma, Pleroma.Mailer * `email`: Email used to reach an Administrator/Moderator of the instance * `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance`` * `limit`: Posts character limit (CW/Subject included in the counter) +* `remote_limit`: Hard character limit beyond which remote posts will be dropped. * `upload_limit`: File size limit of uploads (except for avatar, background, banner) * `avatar_upload_limit`: File size limit of user’s profile avatars * `background_upload_limit`: File size limit of user’s profile backgrounds @@@ -122,9 -121,6 +122,9 @@@ This section is used to configure Plero * `allow_followersonly`: whether to allow followers-only posts * `allow_direct`: whether to allow direct messages +## :mrf_hellthread +* `threshold`: Number of mentioned users after which the message gets discarded as spam + ## :media_proxy * `enabled`: Enables proxying of remote media to the instance’s proxy * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts. @@@ -172,7 -168,7 +172,7 @@@ Web Push Notifications configuration. Y ## Pleroma.Captcha * `enabled`: Whether the captcha should be shown on registration * `method`: The method/service to use for captcha - * `seconds_retained`: The time in seconds for which the captcha is valid (stored in the cache) + * `seconds_valid`: The time in seconds for which the captcha is valid ### Pleroma.Captcha.Kocaptcha Kocaptcha is a very simple captcha service with a single API endpoint, @@@ -193,14 -189,3 +193,14 @@@ You can then d ``` curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerandomtoken" ``` + +## Pleroma.Web.Federator + +* `max_jobs`: The maximum amount of parallel federation jobs running at the same time. + +## Pleroma.Web.Federator.RetryQueue + +* `enabled`: If set to `true`, failed federation jobs will be retried +* `max_jobs`: The maximum amount of parallel federation jbos running at the same time. +* `initial_timeout`: The initial timeout in seconds +* `max_retries`: The maximum number of times a federation job is retried diff --combined lib/pleroma/application.ex index 4542ed623,8dbacf258..cb3e6b69b --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@@ -1,7 -1,3 +1,7 @@@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Application do use Application import Supervisor.Spec @@@ -29,6 -25,16 +29,16 @@@ supervisor(Pleroma.Repo, []), worker(Pleroma.Emoji, []), worker(Pleroma.Captcha, []), + worker( + Cachex, + [ + :used_captcha_cache, + [ + ttl_interval: :timer.seconds(Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid])) + ] + ], + id: :cachex_used_captcha_cache + ), worker( Cachex, [ @@@ -53,16 -59,6 +63,16 @@@ ], id: :cachex_object ), + worker( + Cachex, + [ + :scrubber_cache, + [ + limit: 2500 + ] + ], + id: :cachex_scrubber + ), worker( Cachex, [ diff --combined lib/pleroma/captcha/captcha.ex index 133a9fd68,424ad4add..0207bcbea --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@@ -1,11 -1,9 +1,13 @@@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Captcha do - use GenServer + alias Plug.Crypto.KeyGenerator + alias Plug.Crypto.MessageEncryptor + alias Calendar.DateTime - @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}] + use GenServer @doc false def start_link() do @@@ -14,14 -12,6 +16,6 @@@ @doc false def init(_) do - # Create a ETS table to store captchas - ets_name = Module.concat(method(), Ets) - ^ets_name = :ets.new(Module.concat(method(), Ets), @ets_options) - - # Clean up old captchas every few minutes - seconds_retained = Pleroma.Config.get!([__MODULE__, :seconds_retained]) - Process.send_after(self(), :cleanup, 1000 * seconds_retained) - {:ok, nil} end @@@ -35,8 -25,8 +29,8 @@@ @doc """ Ask the configured captcha service to validate the captcha """ - def validate(token, captcha) do - GenServer.call(__MODULE__, {:validate, token, captcha}) + def validate(token, captcha, answer_data) do + GenServer.call(__MODULE__, {:validate, token, captcha, answer_data}) end @doc false @@@ -46,24 -36,71 +40,71 @@@ if !enabled do {:reply, %{type: :none}, state} else - {:reply, method().new(), state} + new_captcha = method().new() + + secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base]) + + # This make salt a little different for two keys + token = new_captcha[:token] + secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") + sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") + # Basicallty copy what Phoenix.Token does here, add the time to + # the actual data and make it a binary to then encrypt it + encrypted_captcha_answer = + %{ + at: DateTime.now_utc(), + answer_data: new_captcha[:answer_data] + } + |> :erlang.term_to_binary() + |> MessageEncryptor.encrypt(secret, sign_secret) + + { + :reply, + # Repalce the answer with the encrypted answer + %{new_captcha | answer_data: encrypted_captcha_answer}, + state + } end end @doc false - def handle_call({:validate, token, captcha}, _from, state) do - {:reply, method().validate(token, captcha), state} - end + def handle_call({:validate, token, captcha, answer_data}, _from, state) do + secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base]) + secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") + sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") - @doc false - def handle_info(:cleanup, state) do - :ok = method().cleanup() + # If the time found is less than (current_time - seconds_valid), then the time has already passed. + # Later we check that the time found is more than the presumed invalidatation time, that means + # that the data is still valid and the captcha can be checked + seconds_valid = Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]) + valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid) + + result = + with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), + %{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do + try do + if DateTime.before?(at, valid_if_after), do: throw({:error, "CAPTCHA expired"}) + + if not is_nil(Cachex.get!(:used_captcha_cache, token)), + do: throw({:error, "CAPTCHA already used"}) + + res = method().validate(token, captcha, answer_md5) + # Throw if an error occurs + if res != :ok, do: throw(res) + + # Mark this captcha as used + {:ok, _} = + Cachex.put(:used_captcha_cache, token, true, ttl: :timer.seconds(seconds_valid)) - seconds_retained = Pleroma.Config.get!([__MODULE__, :seconds_retained]) - # Schedule the next clenup - Process.send_after(self(), :cleanup, 1000 * seconds_retained) + :ok + catch + :throw, e -> e + end + else + _ -> {:error, "Invalid answer data"} + end - {:noreply, state} + {:reply, result, state} end defp method, do: Pleroma.Config.get!([__MODULE__, :method]) diff --combined lib/pleroma/captcha/captcha_service.ex index a820751a8,6c5ab6c36..8d27c04f1 --- a/lib/pleroma/captcha/captcha_service.ex +++ b/lib/pleroma/captcha/captcha_service.ex @@@ -1,16 -1,17 +1,21 @@@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Captcha.Service do @doc """ Request new captcha from a captcha service. Returns: - Service-specific data for using the newly created captcha + Type/Name of the service, the token to identify the captcha, + the data of the answer and service-specific data to use the newly created captcha """ - @callback new() :: map + @callback new() :: %{ + type: atom(), + token: String.t(), + answer_data: any() + } @doc """ Validated the provided captcha solution. @@@ -18,15 -19,15 +23,15 @@@ Arguments: * `token` the captcha is associated with * `captcha` solution of the captcha to validate + * `answer_data` is the data needed to validate the answer (presumably encrypted) Returns: `true` if captcha is valid, `false` if not """ - @callback validate(token :: String.t(), captcha :: String.t()) :: boolean - - @doc """ - This function is called periodically to clean up old captchas - """ - @callback cleanup() :: :ok + @callback validate( + token :: String.t(), + captcha :: String.t(), + answer_data :: any() + ) :: :ok | {:error, String.t()} end diff --combined lib/pleroma/captcha/kocaptcha.ex index 66f9ce754,cd0eb6f21..34a611492 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@@ -1,15 -1,7 +1,11 @@@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Captcha.Kocaptcha do - alias Calendar.DateTime - alias Pleroma.Captcha.Service @behaviour Service - @ets __MODULE__.Ets - @impl Service def new() do endpoint = Pleroma.Config.get!([__MODULE__, :endpoint]) @@@ -21,51 -13,21 +17,21 @@@ {:ok, res} -> json_resp = Poison.decode!(res.body) - token = json_resp["token"] - - true = - :ets.insert( - @ets, - {token, json_resp["md5"], DateTime.now_utc() |> DateTime.Format.unix()} - ) - - %{type: :kocaptcha, token: token, url: endpoint <> json_resp["url"]} - end - end - - @impl Service - def validate(token, captcha) do - with false <- is_nil(captcha), - [{^token, saved_md5, _}] <- :ets.lookup(@ets, token), - true <- :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(saved_md5) do - # Clear the saved value - :ets.delete(@ets, token) - - true - else - _ -> false + %{ + type: :kocaptcha, + token: json_resp["token"], + url: endpoint <> json_resp["url"], + answer_data: json_resp["md5"] + } end end @impl Service - def cleanup() do - seconds_retained = Pleroma.Config.get!([Pleroma.Captcha, :seconds_retained]) - # If the time in ETS is less than current_time - seconds_retained, then the time has - # already passed - delete_after = - DateTime.subtract!(DateTime.now_utc(), seconds_retained) |> DateTime.Format.unix() - - :ets.select_delete( - @ets, - [ - { - {:_, :_, :"$1"}, - [{:<, :"$1", {:const, delete_after}}], - [true] - } - ] - ) - - :ok + def validate(_token, captcha, answer_data) do + # Here the token is unsed, because the unencrypted captcha answer is just passed to method + if not is_nil(captcha) and + :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_data), + do: :ok, + else: {:error, "Invalid CAPTCHA"} end end diff --combined lib/pleroma/web/twitter_api/twitter_api.ex index 0aa4a8d23,9e15f2c33..ecf81d492 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@@ -1,7 -1,3 +1,7 @@@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.TwitterAPI.TwitterAPI do alias Pleroma.{UserInviteToken, User, Activity, Repo, Object} alias Pleroma.{UserEmail, Mailer} @@@ -140,22 -136,28 +140,28 @@@ password: params["password"], password_confirmation: params["confirm"], captcha_solution: params["captcha_solution"], - captcha_token: params["captcha_token"] + captcha_token: params["captcha_token"], + captcha_answer_data: params["captcha_answer_data"] } captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled]) # true if captcha is disabled or enabled and valid, false otherwise captcha_ok = if !captcha_enabled do - true + :ok else - Pleroma.Captcha.validate(params[:captcha_token], params[:captcha_solution]) + Pleroma.Captcha.validate( + params[:captcha_token], + params[:captcha_solution], + params[:captcha_answer_data] + ) end # Captcha invalid - if not captcha_ok do + if captcha_ok != :ok do + {:error, error} = captcha_ok # I have no idea how this error handling works - {:error, %{error: Jason.encode!(%{captcha: ["Invalid CAPTCHA"]})}} + {:error, %{error: Jason.encode!(%{captcha: [error]})}} else registrations_open = Pleroma.Config.get([:instance, :registrations_open]) diff --combined test/captcha_test.exs index 7f559ac72,93b8930da..7ca9a4607 --- a/test/captcha_test.exs +++ b/test/captcha_test.exs @@@ -1,7 -1,3 +1,7 @@@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.CaptchaTest do use ExUnit.Case @@@ -29,16 -25,18 +29,18 @@@ end test "new and validate" do - assert Kocaptcha.new() == %{ - type: :kocaptcha, - token: "afa1815e14e29355e6c8f6b143a39fa2", - url: "https://captcha.kotobank.ch/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" - } + new = Kocaptcha.new() + assert new[:type] == :kocaptcha + assert new[:token] == "afa1815e14e29355e6c8f6b143a39fa2" + + assert new[:url] == + "https://captcha.kotobank.ch/captchas/afa1815e14e29355e6c8f6b143a39fa2.png" assert Kocaptcha.validate( - "afa1815e14e29355e6c8f6b143a39fa2", - "7oEy8c" - ) + new[:token], + "7oEy8c", + new[:answer_data] + ) == :ok end end end diff --combined test/support/captcha_mock.ex index 3ab02916f,410318dc4..9061f2b45 --- a/test/support/captcha_mock.ex +++ b/test/support/captcha_mock.ex @@@ -1,7 -1,3 +1,7 @@@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Captcha.Mock do alias Pleroma.Captcha.Service @behaviour Service @@@ -10,8 -6,5 +10,5 @@@ def new(), do: %{type: :mock} @impl Service - def validate(_token, _captcha), do: true - - @impl Service - def cleanup(), do: :ok + def validate(_token, _captcha, _data), do: :ok end