From 203d61b95012fd2cb8a8618f6f51a3748c940cc1 Mon Sep 17 00:00:00 2001
From: lain <lain@soykaf.club>
Date: Thu, 17 Oct 2019 19:35:31 +0200
Subject: [PATCH] Transmogrifier: Make proper use of the LikeValidator.

---
 .../web/activity_pub/object_validator.ex      | 10 ++-
 .../object_validators/like_validator.ex       |  2 +-
 .../web/activity_pub/transmogrifier.ex        | 79 +++++++++++++------
 .../activity_pub/object_validator_test.exs    |  2 +
 test/web/activity_pub/transmogrifier_test.exs |  4 +-
 5 files changed, 68 insertions(+), 29 deletions(-)

diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index adcb53c65..33e67dbb9 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -10,6 +10,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   """
 
   alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+  alias Pleroma.User
+  alias Pleroma.Object
 
   @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
   def validate(object, meta)
@@ -24,9 +26,15 @@ def validate(%{"type" => "Like"} = object, meta) do
     end
   end
 
-  defp stringify_keys(object) do
+  def stringify_keys(object) do
     object
     |> Enum.map(fn {key, val} -> {to_string(key), val} end)
     |> Enum.into(%{})
   end
+
+  def fetch_actor_and_object(object) do
+    User.get_or_fetch_by_ap_id(object["actor"])
+    Object.normalize(object["object"])
+    :ok
+  end
 end
diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
index d5a2f7202..e6a5aaca8 100644
--- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
@@ -33,7 +33,7 @@ def cast_data(data) do
   def validate_data(data_cng) do
     data_cng
     |> validate_inclusion(:type, ["Like"])
-    |> validate_required([:id, :type, :object, :actor, :context])
+    |> validate_required([:id, :type, :object, :actor, :context, :to, :cc])
     |> validate_change(:actor, &actor_valid?/2)
     |> validate_change(:object, &object_valid?/2)
     |> validate_existing_like()
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 3e982adcb..591d7aa94 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -16,6 +16,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.Federator
   alias Pleroma.Workers.TransmogrifierWorker
+  alias Pleroma.Web.ActivityPub.ObjectValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
 
   import Ecto.Query
 
@@ -562,39 +564,21 @@ def handle_incoming(
     end
   end
 
-  def handle_incoming(
-        %{"type" => "Like", "object" => _object_id, "actor" => _actor, "id" => _id} = data,
-        _options
-      ) do
-    with data <- Map.take(data, ["type", "object", "actor", "context", "id"]),
-         actor <- Containment.get_actor(data),
-         object <- Containment.get_object(data),
-         data <- data |> Map.put("actor", actor) |> Map.put("object", object),
-         _user <- User.get_or_fetch_by_ap_id(actor),
-         object <- Object.normalize(object),
-         data <- Map.put_new(data, "context", object.data["context"]),
+  def handle_incoming(%{"type" => "Like"} = data, _options) do
+    with {_, %{changes: cast_data}} <- {:casting_data, LikeValidator.cast_data(data)},
+         cast_data <- ObjectValidator.stringify_keys(cast_data),
+         :ok <- ObjectValidator.fetch_actor_and_object(cast_data),
+         {_, {:ok, cast_data}} <- {:maybe_add_context, maybe_add_context_from_object(cast_data)},
+         {_, {:ok, cast_data}} <-
+           {:maybe_add_recipients, maybe_add_recipients_from_object(cast_data)},
          {_, {:ok, activity, _meta}} <-
-           {:common_pipeline, ActivityPub.common_pipeline(data, local: false)} do
+           {:common_pipeline, ActivityPub.common_pipeline(cast_data, local: false)} do
       {:ok, activity}
     else
       e -> {:error, e}
     end
   end
 
-  # def handle_incoming(
-  #       %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data,
-  #       _options
-  #     ) do
-  #   with actor <- Containment.get_actor(data),
-  #        {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
-  #        {:ok, object} <- get_obj_helper(object_id),
-  #        {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do
-  #     {:ok, activity}
-  #   else
-  #     _e -> :error
-  #   end
-  # end
-
   def handle_incoming(
         %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data,
         _options
@@ -1156,4 +1140,47 @@ def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do
   def maybe_fix_user_url(data), do: data
 
   def maybe_fix_user_object(data), do: maybe_fix_user_url(data)
+
+  defp maybe_add_context_from_object(%{"context" => context} = data) when is_binary(context),
+    do: {:ok, data}
+
+  defp maybe_add_context_from_object(%{"object" => object} = data) when is_binary(object) do
+    if object = Object.normalize(object) do
+      data =
+        data
+        |> Map.put("context", object.data["context"])
+
+      {:ok, data}
+    else
+      {:error, "No context on referenced object"}
+    end
+  end
+
+  defp maybe_add_context_from_object(_) do
+    {:error, "No referenced object"}
+  end
+
+  defp maybe_add_recipients_from_object(%{"object" => object} = data) do
+    to = data["to"] || []
+    cc = data["cc"] || []
+
+    if to == [] && cc == [] do
+      if object = Object.normalize(object) do
+        data =
+          data
+          |> Map.put("to", [object.data["actor"]])
+          |> Map.put("cc", cc)
+
+        {:ok, data}
+      else
+        {:error, "No actor on referenced object"}
+      end
+    else
+      {:ok, data}
+    end
+  end
+
+  defp maybe_add_recipients_from_object(_) do
+    {:error, "No referenced object"}
+  end
 end
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index 374a7c0df..2292db6d7 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -13,6 +13,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
       {:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"})
 
       valid_like = %{
+        "to" => [user.ap_id],
+        "cc" => [],
         "type" => "Like",
         "id" => Utils.generate_activity_id(),
         "object" => post_activity.data["object"],
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index 28edc5508..e5d4dcd64 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -333,7 +333,9 @@ test "it works for incoming likes" do
         |> Poison.decode!()
         |> Map.put("object", activity.data["object"])
 
-      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+      {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
+
+      refute Enum.empty?(activity.recipients)
 
       assert data["actor"] == "http://mastodon.example.org/users/admin"
       assert data["type"] == "Like"