UndoValidator: Add UndoValidator.
authorlain <lain@soykaf.club>
Tue, 5 May 2020 12:17:47 +0000 (14:17 +0200)
committerlain <lain@soykaf.club>
Tue, 5 May 2020 12:17:47 +0000 (14:17 +0200)
lib/pleroma/web/activity_pub/builder.ex
lib/pleroma/web/activity_pub/object_validator.ex
lib/pleroma/web/activity_pub/object_validators/common_validations.ex
lib/pleroma/web/activity_pub/object_validators/undo_validator.ex [new file with mode: 0644]
test/web/activity_pub/object_validator_test.exs

index 429a510b8118df4f136f614de281131fd5dd78b8..380d8f56514a23cd2f48b7e1501e1b229fc8153a 100644 (file)
@@ -10,6 +10,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.ActivityPub.Visibility
 
+  @spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()}
+  def undo(actor, object) do
+    {:ok,
+     %{
+       "id" => Utils.generate_activity_id(),
+       "actor" => actor.ap_id,
+       "type" => "Undo",
+       "object" => object.data["id"],
+       "to" => object.data["to"] || [],
+       "cc" => object.data["cc"] || []
+     }, []}
+  end
+
   @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
   def like(actor, object) do
     object_actor = User.get_cached_by_ap_id(object.data["actor"])
index dc4bce0595a12c409475206b2aed0e7222b7ce18..b6937d2e1cc8cc0608ba7f673ad4b5fb7379dd50 100644 (file)
@@ -12,10 +12,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+  alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
 
   @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
   def validate(object, meta)
 
+  def validate(%{"type" => "Undo"} = object, meta) do
+    with {:ok, object} <-
+           object |> UndoValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do
+      object = stringify_keys(object |> Map.from_struct())
+      {:ok, object, meta}
+    end
+  end
+
   def validate(%{"type" => "Like"} = object, meta) do
     with {:ok, object} <-
            object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do
index b479c391837f1ccf20dfabd5134b562054e4dbb8..067ee4f9aa686e9099d5a65b9df54468aff68d44 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
   import Ecto.Changeset
 
+  alias Pleroma.Activity
   alias Pleroma.Object
   alias Pleroma.User
 
@@ -22,7 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
   def validate_object_presence(cng, field_name \\ :object) do
     cng
     |> validate_change(field_name, fn field_name, object ->
-      if Object.get_cached_by_ap_id(object) do
+      if Object.get_cached_by_ap_id(object) || Activity.get_by_ap_id(object) do
         []
       else
         [{field_name, "can't find object"}]
diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
new file mode 100644 (file)
index 0000000..d0ba418
--- /dev/null
@@ -0,0 +1,62 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
+  use Ecto.Schema
+
+  alias Pleroma.Activity
+  alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+
+  import Ecto.Changeset
+  import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+  @primary_key false
+
+  embedded_schema do
+    field(:id, Types.ObjectID, primary_key: true)
+    field(:type, :string)
+    field(:object, Types.ObjectID)
+    field(:actor, Types.ObjectID)
+    field(:to, {:array, :string}, default: [])
+    field(:cc, {:array, :string}, default: [])
+  end
+
+  def cast_and_validate(data) do
+    data
+    |> cast_data()
+    |> validate_data()
+  end
+
+  def cast_data(data) do
+    %__MODULE__{}
+    |> changeset(data)
+  end
+
+  def changeset(struct, data) do
+    struct
+    |> cast(data, __schema__(:fields))
+  end
+
+  def validate_data(data_cng) do
+    data_cng
+    |> validate_inclusion(:type, ["Undo"])
+    |> validate_required([:id, :type, :object, :actor, :to, :cc])
+    |> validate_actor_presence()
+    |> validate_object_presence()
+    |> validate_undo_rights()
+  end
+
+  def validate_undo_rights(cng) do
+    actor = get_field(cng, :actor)
+    object = get_field(cng, :object)
+
+    with %Activity{data: %{"actor" => object_actor}} <- Activity.get_by_ap_id(object),
+         true <- object_actor != actor do
+      cng
+      |> add_error(:actor, "not the same as object actor")
+    else
+      _ -> cng
+    end
+  end
+end
index 93989e28ae6d7efce9a136c5c5e21224cd7d9528..8626e127e1ee81650e7a06080f3b5881311ee00a 100644 (file)
@@ -1,6 +1,7 @@
 defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
   use Pleroma.DataCase
 
+  alias Pleroma.Web.ActivityPub.Builder
   alias Pleroma.Web.ActivityPub.ObjectValidator
   alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
   alias Pleroma.Web.ActivityPub.Utils
@@ -8,6 +9,46 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
 
   import Pleroma.Factory
 
+  describe "Undos" do
+    setup do
+      user = insert(:user)
+      {:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"})
+      {:ok, like} = CommonAPI.favorite(user, post_activity.id)
+      {:ok, valid_like_undo, []} = Builder.undo(user, like)
+
+      %{user: user, like: like, valid_like_undo: valid_like_undo}
+    end
+
+    test "it validates a basic like undo", %{valid_like_undo: valid_like_undo} do
+      assert {:ok, _, _} = ObjectValidator.validate(valid_like_undo, [])
+    end
+
+    test "it does not validate if the actor of the undo is not the actor of the object", %{
+      valid_like_undo: valid_like_undo
+    } do
+      other_user = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")
+
+      bad_actor =
+        valid_like_undo
+        |> Map.put("actor", other_user.ap_id)
+
+      {:error, cng} = ObjectValidator.validate(bad_actor, [])
+
+      assert {:actor, {"not the same as object actor", []}} in cng.errors
+    end
+
+    test "it does not validate if the object is missing", %{valid_like_undo: valid_like_undo} do
+      missing_object =
+        valid_like_undo
+        |> Map.put("object", "https://gensokyo.2hu/objects/1")
+
+      {:error, cng} = ObjectValidator.validate(missing_object, [])
+
+      assert {:object, {"can't find object", []}} in cng.errors
+      assert length(cng.errors) == 1
+    end
+  end
+
   describe "likes" do
     setup do
       user = insert(:user)