ChatMessages: Better validation.
authorlain <lain@soykaf.club>
Thu, 16 Apr 2020 13:21:47 +0000 (15:21 +0200)
committerlain <lain@soykaf.club>
Thu, 16 Apr 2020 13:21:47 +0000 (15:21 +0200)
lib/pleroma/web/activity_pub/object_validator.ex
lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
lib/pleroma/web/activity_pub/object_validators/common_validations.ex
lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
lib/pleroma/web/activity_pub/transmogrifier/chat_message_handling.ex
test/fixtures/create-chat-message.json
test/web/activity_pub/object_validator_test.exs
test/web/activity_pub/transmogrifier/chat_message_test.exs

index 49cc7256127778ad738073ef443528a04840376f..259bbeb6477f92c95789e60821906b47e374a8b6 100644 (file)
@@ -31,7 +31,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   def validate(%{"type" => "ChatMessage"} = object, meta) do
     with {:ok, object} <-
            object
-           |> ChatMessageValidator.cast_and_apply() do
+           |> ChatMessageValidator.cast_and_validate()
+           |> Ecto.Changeset.apply_action(:insert) do
       object = stringify_keys(object)
       {:ok, object, meta}
     end
@@ -40,7 +41,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   def validate(%{"type" => "Create"} = object, meta) do
     with {:ok, object} <-
            object
-           |> CreateChatMessageValidator.cast_and_apply() do
+           |> CreateChatMessageValidator.cast_and_validate()
+           |> Ecto.Changeset.apply_action(:insert) do
       object = stringify_keys(object)
       {:ok, object, meta}
     end
index ab5be35960dd0f974c773ab9836a7962aaba2e23..a4e4460cdb140c87e5e80c7925227bd6c6aedb38 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
   use Ecto.Schema
 
   alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+  alias Pleroma.User
 
   import Ecto.Changeset
 
@@ -54,5 +55,30 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
     data_cng
     |> validate_inclusion(:type, ["ChatMessage"])
     |> validate_required([:id, :actor, :to, :type, :content])
+    |> validate_length(:to, is: 1)
+    |> validate_local_concern()
+  end
+
+  @doc "Validates if at least one of the users in this ChatMessage is a local user, otherwise we don't want the message in our system. It also validates the presence of both users in our system."
+  def validate_local_concern(cng) do
+    with actor_ap <- get_field(cng, :actor),
+         {_, %User{} = actor} <- {:find_actor, User.get_cached_by_ap_id(actor_ap)},
+         {_, %User{} = recipient} <-
+           {:find_recipient, User.get_cached_by_ap_id(get_field(cng, :to) |> hd())},
+         {_, true} <- {:local?, Enum.any?([actor, recipient], & &1.local)} do
+      cng
+    else
+      {:local?, false} ->
+        cng
+        |> add_error(:actor, "actor and recipient are both remote")
+
+      {:find_actor, _} ->
+        cng
+        |> add_error(:actor, "can't find user")
+
+      {:find_recipient, _} ->
+        cng
+        |> add_error(:to, "can't find user")
+    end
   end
 end
index b479c391837f1ccf20dfabd5134b562054e4dbb8..02f3a6438b5bfb4a19a6710d4505d7b6c28cbd79 100644 (file)
@@ -8,7 +8,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
   alias Pleroma.Object
   alias Pleroma.User
 
-  def validate_actor_presence(cng, field_name \\ :actor) do
+  def validate_actor_presence(cng) do
+    validate_user_presence(cng, :actor)
+  end
+
+  def validate_user_presence(cng, field_name) do
     cng
     |> validate_change(field_name, fn field_name, actor ->
       if User.get_cached_by_ap_id(actor) do
index 6593114801304ea60cb80e6e7b96b37b6e9fa846..ce52d56236556c4dc1dcd3c29a3c7dcddf5a3fbd 100644 (file)
@@ -32,4 +32,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
   def cast_data(data) do
     cast(%__MODULE__{}, data, __schema__(:fields))
   end
+
+  # No validation yet
+  def cast_and_validate(data) do
+    cast_data(data)
+  end
 end
index b5843736f0cbc8541422cc8c1bec80cc5617e166..815b866c93663da6e030c5d179e450f449c7c19c 100644 (file)
@@ -25,6 +25,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.ChatMessageHandling do
          {_, {:ok, activity, _meta}} <-
            {:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do
       {:ok, activity}
+    else
+      e ->
+        {:error, e}
     end
   end
 end
index 4aa17f4a55f9b12a6eaf52e2ad6f72d77aaee8ac..2e4608f4335204bf2af5d881491b9bb2c3237bdd 100644 (file)
@@ -3,7 +3,7 @@
     "id": "http://2hu.gensokyo/objects/1",
     "object": {
         "attributedTo": "http://2hu.gensokyo/users/raymoo",
-        "content": "You expected a cute girl? Too bad.",
+        "content": "You expected a cute girl? Too bad. <script>alert('XSS')</script>",
         "id": "http://2hu.gensokyo/objects/2",
         "published": "2020-02-12T14:08:20Z",
         "to": [
index 3c5c3696e299c14e08febf8d52caf83f26ca58b6..bf0bfdfaf4d97b1babc12316674197e0f8df6049 100644 (file)
@@ -5,9 +5,61 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
   alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Web.CommonAPI
+  alias Pleroma.Web.ActivityPub.Builder
 
   import Pleroma.Factory
 
+  describe "chat messages" do
+    setup do
+      user = insert(:user)
+      recipient = insert(:user, local: false)
+
+      {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
+
+      %{user: user, recipient: recipient, valid_chat_message: valid_chat_message}
+    end
+
+    test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
+      assert {:ok, _object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+    end
+
+    test "does not validate if the actor or the recipient is not in our system", %{
+      valid_chat_message: valid_chat_message
+    } do
+      chat_message =
+        valid_chat_message
+        |> Map.put("actor", "https://raymoo.com/raymoo")
+
+      {:error, _} = ObjectValidator.validate(chat_message, [])
+
+      chat_message =
+        valid_chat_message
+        |> Map.put("to", ["https://raymoo.com/raymoo"])
+
+      {:error, _} = ObjectValidator.validate(chat_message, [])
+    end
+
+    test "does not validate for a message with multiple recipients", %{
+      valid_chat_message: valid_chat_message,
+      user: user,
+      recipient: recipient
+    } do
+      chat_message =
+        valid_chat_message
+        |> Map.put("to", [user.ap_id, recipient.ap_id])
+
+      assert {:error, _} = ObjectValidator.validate(chat_message, [])
+    end
+
+    test "does not validate if it doesn't concern local users" do
+      user = insert(:user, local: false)
+      recipient = insert(:user, local: false)
+
+      {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
+      assert {:error, _} = ObjectValidator.validate(valid_chat_message, [])
+    end
+  end
+
   describe "likes" do
     setup do
       user = insert(:user)
index aed62c520e82eda15d4dd6d03c1462d6499b118d..5b238f9c4d4274475308c93c38d5f46b0cfbe9c7 100644 (file)
@@ -12,13 +12,43 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.ChatMessageTest do
   alias Pleroma.Web.ActivityPub.Transmogrifier
 
   describe "handle_incoming" do
-    test "it insert it" do
+    test "it rejects messages that don't contain content" do
+      data =
+        File.read!("test/fixtures/create-chat-message.json")
+        |> Poison.decode!()
+
+      object =
+        data["object"]
+        |> Map.delete("content")
+
+      data =
+        data
+        |> Map.put("object", object)
+
+      _author = insert(:user, ap_id: data["actor"], local: false)
+      _recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
+
+      {:error, _} = Transmogrifier.handle_incoming(data)
+    end
+
+    test "it rejects messages that don't concern local users" do
+      data =
+        File.read!("test/fixtures/create-chat-message.json")
+        |> Poison.decode!()
+
+      _author = insert(:user, ap_id: data["actor"], local: false)
+      _recipient = insert(:user, ap_id: List.first(data["to"]), local: false)
+
+      {:error, _} = Transmogrifier.handle_incoming(data)
+    end
+
+    test "it inserts it and creates a chat" do
       data =
         File.read!("test/fixtures/create-chat-message.json")
         |> Poison.decode!()
 
       author = insert(:user, ap_id: data["actor"], local: false)
-      recipient = insert(:user, ap_id: List.first(data["to"]), local: false)
+      recipient = insert(:user, ap_id: List.first(data["to"]), local: true)
 
       {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data)