Separate captcha implementation into a behaviour and use it
authorEkaterina Vaartis <vaartis@cock.li>
Sat, 15 Dec 2018 19:06:44 +0000 (22:06 +0300)
committerEkaterina Vaartis <vaartis@cock.li>
Sat, 15 Dec 2018 19:07:26 +0000 (22:07 +0300)
lib/pleroma/captcha.ex [deleted file]
lib/pleroma/captcha/captcha.ex [new file with mode: 0644]
lib/pleroma/captcha/captcha_service.ex [new file with mode: 0644]
lib/pleroma/captcha/kocaptcha.ex [new file with mode: 0644]

diff --git a/lib/pleroma/captcha.ex b/lib/pleroma/captcha.ex
deleted file mode 100644 (file)
index ffa5640..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-defmodule Pleroma.Captcha do
-  use GenServer
-
-  @ets __MODULE__.Ets
-  @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}]
-
-
-  @doc false
-  def start_link() do
-    GenServer.start_link(__MODULE__, [], name: __MODULE__)
-  end
-
-
-  @doc false
-  def init(_) do
-    @ets = :ets.new(@ets, @ets_options)
-
-    {:ok, nil}
-  end
-
-  def new() do
-    GenServer.call(__MODULE__, :new)
-  end
-
-  def validate(token, captcha) do
-    GenServer.call(__MODULE__, {:validate, token, captcha})
-  end
-
-  @doc false
-  def handle_call(:new, _from, state) do
-    enabled = Pleroma.Config.get([__MODULE__, :enabled])
-
-    if !enabled do
-      {
-        :reply,
-        %{type: :none},
-        state
-      }
-    else
-      method = Pleroma.Config.get!([__MODULE__, :method])
-
-      case method do
-        __MODULE__.Kocaptcha ->
-          endpoint = Pleroma.Config.get!([method, :endpoint])
-          case HTTPoison.get(endpoint <> "/new") do
-            {:error, _} ->
-              %{error: "Kocaptcha service unavailable"}
-            {:ok, res} ->
-              json_resp = Poison.decode!(res.body)
-
-              token = json_resp["token"]
-
-              true = :ets.insert(@ets, {token, json_resp["md5"]})
-
-              {
-                :reply,
-                %{type: :kocaptcha, token: token, url: endpoint <> json_resp["url"]},
-                state
-              }
-          end
-      end
-    end
-  end
-
-  @doc false
-  def handle_call({:validate, token, captcha}, _from, state) 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)
-
-      {:reply, true, state}
-    else
-      e -> IO.inspect(e); {:reply, false, state}
-    end
-  end
-end
diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex
new file mode 100644 (file)
index 0000000..df33406
--- /dev/null
@@ -0,0 +1,51 @@
+defmodule Pleroma.Captcha do
+  use GenServer
+
+  @ets_options [:ordered_set, :private, :named_table, {:read_concurrency, true}]
+
+  @doc false
+  def start_link() do
+    GenServer.start_link(__MODULE__, [], name: __MODULE__)
+  end
+
+  @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)
+
+    {:ok, nil}
+  end
+
+  @doc """
+  Ask the configured captcha service for a new captcha
+  """
+  def new() do
+    GenServer.call(__MODULE__, :new)
+  end
+
+  @doc """
+  Ask the configured captcha service to validate the captcha
+  """
+  def validate(token, captcha) do
+    GenServer.call(__MODULE__, {:validate, token, captcha})
+  end
+
+  @doc false
+  def handle_call(:new, _from, state) do
+    enabled = Pleroma.Config.get([__MODULE__, :enabled])
+
+    if !enabled do
+      {:reply, %{type: :none}, state}
+    else
+      {:reply, method().new(), state}
+    end
+  end
+
+  @doc false
+  def handle_call({:validate, token, captcha}, _from, state) do
+    {:reply, method().validate(token, captcha), state}
+  end
+
+  defp method, do: Pleroma.Config.get!([__MODULE__, :method])
+end
diff --git a/lib/pleroma/captcha/captcha_service.ex b/lib/pleroma/captcha/captcha_service.ex
new file mode 100644 (file)
index 0000000..ae1d6e7
--- /dev/null
@@ -0,0 +1,24 @@
+defmodule Pleroma.Captcha.Service do
+
+  @doc """
+  Request new captcha from a captcha service.
+
+  Returns:
+
+  Service-specific data for using the newly created captcha
+  """
+  @callback new() :: map
+
+  @doc """
+  Validated the provided captcha solution.
+
+  Arguments:
+  * `token` the captcha is associated with
+  * `captcha` solution of the captcha to validate
+
+  Returns:
+
+  `true` if captcha is valid, `false` if not
+  """
+  @callback validate(token :: String.t, captcha :: String.t) :: boolean
+end
diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex
new file mode 100644 (file)
index 0000000..abccbf6
--- /dev/null
@@ -0,0 +1,37 @@
+defmodule Pleroma.Captcha.Kocaptcha do
+  alias Pleroma.Captcha.Service
+  @behaviour Service
+
+  @ets __MODULE__.Ets
+
+  @impl Service
+  def new() do
+    endpoint = Pleroma.Config.get!([__MODULE__, :endpoint])
+    case HTTPoison.get(endpoint <> "/new") do
+      {:error, _} ->
+        %{error: "Kocaptcha service unavailable"}
+      {:ok, res} ->
+        json_resp = Poison.decode!(res.body)
+
+        token = json_resp["token"]
+
+        true = :ets.insert(@ets, {token, json_resp["md5"]})
+
+        %{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
+    end
+  end
+end