Move the encryption out of kocaptcha into general captcha module
authorEkaterina Vaartis <vaartis@cock.li>
Sat, 22 Dec 2018 19:39:08 +0000 (22:39 +0300)
committerEkaterina Vaartis <vaartis@cock.li>
Sat, 22 Dec 2018 19:42:14 +0000 (22:42 +0300)
That way there won't be a need to reimplement it for other captcha services

lib/pleroma/captcha/captcha.ex
lib/pleroma/captcha/captcha_service.ex
lib/pleroma/captcha/kocaptcha.ex

index 61a0f907f42bc78e5c19bdcfd3a00899796e5f39..04769d4b2b87582458fde62653df1a0474c86167 100644 (file)
@@ -1,4 +1,8 @@
 defmodule Pleroma.Captcha do
+  alias Plug.Crypto.KeyGenerator
+  alias Plug.Crypto.MessageEncryptor
+  alias Calendar.DateTime
+
   use GenServer
 
   @doc false
@@ -32,13 +36,58 @@ defmodule Pleroma.Captcha do
     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)
+
+      IO.inspect(%{new_captcha | answer_data: encrypted_captcha_answer})
+
+      {
+        :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, answer_data}, _from, state) do
-    {:reply, method().validate(token, captcha, answer_data), state}
+    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")
+
+    # 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
+        if DateTime.after?(at, valid_if_after),
+          do: method().validate(token, captcha, answer_md5),
+          else: {:error, "CAPTCHA expired"}
+      else
+        _ -> {:error, "Invalid answer data"}
+      end
+
+    {:reply, result, state}
   end
 
   defp method, do: Pleroma.Config.get!([__MODULE__, :method])
index 6f36d29b013b4a6a038b640d63645872609b6c78..6c5ab6c36b7af7840004527384bbb5e21bb5032a 100644 (file)
@@ -4,9 +4,14 @@ defmodule Pleroma.Captcha.Service do
 
   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.
@@ -23,6 +28,6 @@ defmodule Pleroma.Captcha.Service do
   @callback validate(
               token :: String.t(),
               captcha :: String.t(),
-              answer_data :: String.t()
+              answer_data :: any()
             ) :: :ok | {:error, String.t()}
 end
index f881c7b652a00303608763dd692e0d7d788de10f..cd0eb6f2141d0360c69f710ad7a5ed9bd2649d27 100644 (file)
@@ -1,8 +1,4 @@
 defmodule Pleroma.Captcha.Kocaptcha do
-  alias Plug.Crypto.KeyGenerator
-  alias Plug.Crypto.MessageEncryptor
-  alias Calendar.DateTime
-
   alias Pleroma.Captcha.Service
   @behaviour Service
 
@@ -17,57 +13,21 @@ defmodule Pleroma.Captcha.Kocaptcha do
       {:ok, res} ->
         json_resp = Poison.decode!(res.body)
 
-        token = json_resp["token"]
-        answer_md5 = json_resp["md5"]
-
-        secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base])
-
-        # This make salt a little different for two keys
-        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_md5: answer_md5
-          }
-          |> :erlang.term_to_binary()
-          |> MessageEncryptor.encrypt(secret, sign_secret)
-
         %{
           type: :kocaptcha,
-          token: token,
+          token: json_resp["token"],
           url: endpoint <> json_resp["url"],
-          answer_data: encrypted_captcha_answer
+          answer_data: json_resp["md5"]
         }
     end
   end
 
   @impl Service
-  def validate(token, captcha, answer_data) 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")
-
-    # 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)
-
-    with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
-         %{at: at, answer_md5: answer_md5} <- :erlang.binary_to_term(data) do
-      if DateTime.after?(at, valid_if_after) do
-        if not is_nil(captcha) and
-             :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_md5),
-           do: :ok,
-           else: {:error, "Invalid CAPTCHA"}
-      else
-        {:error, "CAPTCHA expired"}
-      end
-    else
-      _ -> {:error, "Invalid answer data"}
-    end
+  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