Merge branch 'develop' into feature/database-compaction
authorrinpatch <rinpatch@sdf.org>
Wed, 17 Apr 2019 09:22:32 +0000 (12:22 +0300)
committerrinpatch <rinpatch@sdf.org>
Wed, 17 Apr 2019 09:22:32 +0000 (12:22 +0300)
30 files changed:
lib/mix/tasks/compact_database.ex [new file with mode: 0644]
lib/pleroma/activity.ex
lib/pleroma/gopher/server.ex
lib/pleroma/html.ex
lib/pleroma/object.ex
lib/pleroma/object/containment.ex [new file with mode: 0644]
lib/pleroma/object/fetcher.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/activity_pub_controller.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/common_api/utils.ex
lib/pleroma/web/federator/federator.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/ostatus/activity_representer.ex
lib/pleroma/web/ostatus/handlers/note_handler.ex
lib/pleroma/web/twitter_api/controllers/util_controller.ex
lib/pleroma/web/twitter_api/views/activity_view.ex
test/object/containment_test.exs [new file with mode: 0644]
test/object/fetcher_test.exs [new file with mode: 0644]
test/object_test.exs
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/transmogrifier_test.exs
test/web/common_api/common_api_test.exs
test/web/mastodon_api/status_view_test.exs
test/web/ostatus/ostatus_test.exs
test/web/twitter_api/twitter_api_test.exs
test/web/twitter_api/views/activity_view_test.exs

diff --git a/lib/mix/tasks/compact_database.ex b/lib/mix/tasks/compact_database.ex
new file mode 100644 (file)
index 0000000..17b9721
--- /dev/null
@@ -0,0 +1,57 @@
+defmodule Mix.Tasks.CompactDatabase do
+  @moduledoc """
+  Compact the database by flattening the object graph.
+  """
+
+  require Logger
+
+  use Mix.Task
+  import Ecto.Query
+  alias Pleroma.Activity
+  alias Pleroma.Repo
+
+  defp maybe_compact(%Activity{data: %{"object" => %{"id" => object_id}}} = activity) do
+    data =
+      activity.data
+      |> Map.put("object", object_id)
+
+    {:ok, activity} =
+      Activity.change(activity, %{data: data})
+      |> Repo.update()
+
+    {:ok, activity}
+  end
+
+  defp maybe_compact(%Activity{} = activity), do: {:ok, activity}
+
+  defp activity_query(min_id, max_id) do
+    from(
+      a in Activity,
+      where: fragment("?->>'type' = 'Create'", a.data),
+      where: a.id >= ^min_id,
+      where: a.id < ^max_id
+    )
+  end
+
+  def run(_args) do
+    Application.ensure_all_started(:pleroma)
+
+    max = Repo.aggregate(Activity, :max, :id)
+    Logger.info("Considering #{max} activities")
+
+    chunks = 0..round(max / 100)
+
+    Enum.each(chunks, fn i ->
+      min = i * 100
+      max = min + 100
+
+      activity_query(min, max)
+      |> Repo.all()
+      |> Enum.each(&maybe_compact/1)
+
+      IO.write(".")
+    end)
+
+    Logger.info("Finished.")
+  end
+end
index e6507e5ca011b342b8a656c6ce20e1476332eaaa..99cc9c077cb131472711e3e26a98ad880e0709ee 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.Activity do
   alias Pleroma.Object
   alias Pleroma.Repo
 
+  import Ecto.Changeset
   import Ecto.Query
 
   @type t :: %__MODULE__{}
@@ -79,6 +80,13 @@ defmodule Pleroma.Activity do
     )
   end
 
+  def change(struct, params \\ %{}) do
+    struct
+    |> cast(params, [:data])
+    |> validate_required([:data])
+    |> unique_constraint(:ap_id, name: :activities_unique_apid_index)
+  end
+
   def get_by_ap_id_with_object(ap_id) do
     Repo.one(
       from(
@@ -202,15 +210,19 @@ defmodule Pleroma.Activity do
     |> Repo.one()
   end
 
-  def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
-  def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
-  def normalize(_), do: nil
+  defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}) do
+    get_create_by_object_ap_id_with_object(ap_id)
+  end
+
+  defp get_in_reply_to_activity_from_object(_), do: nil
 
-  def get_in_reply_to_activity(%Activity{data: %{"object" => %{"inReplyTo" => ap_id}}}) do
-    get_create_by_object_ap_id(ap_id)
+  def get_in_reply_to_activity(%Activity{data: %{"object" => object}}) do
+    get_in_reply_to_activity_from_object(Object.normalize(object))
   end
 
-  def get_in_reply_to_activity(_), do: nil
+  def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
+  def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
+  def normalize(_), do: nil
 
   def delete_by_ap_id(id) when is_binary(id) do
     by_object_ap_id(id)
index 6a56a6f677c051debc6b8355b1fb442ef5e405b9..2ebc5d5f7a8ebc306d34904f2c97b4f2a2a95ff4 100644 (file)
@@ -38,6 +38,7 @@ end
 defmodule Pleroma.Gopher.Server.ProtocolHandler do
   alias Pleroma.Activity
   alias Pleroma.HTML
+  alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Visibility
@@ -75,14 +76,14 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
     |> Enum.map(fn activity ->
       user = User.get_cached_by_ap_id(activity.data["actor"])
 
-      object = activity.data["object"]
+      object = Object.normalize(activity.data["object"])
       like_count = object["like_count"] || 0
       announcement_count = object["announcement_count"] || 0
 
       link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
         info("#{like_count} likes, #{announcement_count} repeats") <>
         "i\tfake\t(NULL)\t0\r\n" <>
-        info(HTML.strip_tags(String.replace(activity.data["object"]["content"], "<br>", "\r")))
+        info(HTML.strip_tags(String.replace(object["content"], "<br>", "\r")))
     end)
     |> Enum.join("i\tfake\t(NULL)\t0\r\n")
   end
index 7f1dbe28c81ce1e9a94cb43ae96bc0291d8cfb17..4b42d8c9b93a178de7d8f120a8a3a8f044672230 100644 (file)
@@ -32,7 +32,8 @@ defmodule Pleroma.HTML do
     key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
 
     Cachex.fetch!(:scrubber_cache, key, fn _key ->
-      ensure_scrubbed_html(content, scrubbers, activity.data["object"]["fake"] || false)
+      object = Pleroma.Object.normalize(activity)
+      ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false)
     end)
   end
 
index 013d6215710ec42858ab18ed3be69e6775e405ed..3f1d0fea1f28e67651195f337c09f9f16cf214c2 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Object do
 
   alias Pleroma.Activity
   alias Pleroma.Object
+  alias Pleroma.Object.Fetcher
   alias Pleroma.ObjectTombstone
   alias Pleroma.Repo
   alias Pleroma.User
@@ -40,41 +41,43 @@ defmodule Pleroma.Object do
     Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
   end
 
+  def normalize(_, fetch_remote \\ true)
   # If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
   # Use this whenever possible, especially when walking graphs in an O(N) loop!
-  def normalize(%Activity{object: %Object{} = object}), do: object
+  def normalize(%Activity{object: %Object{} = object}, _), do: object
 
   # A hack for fake activities
-  def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}) do
+  def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _) do
     %Object{id: "pleroma:fake_object_id", data: data}
   end
 
   # Catch and log Object.normalize() calls where the Activity's child object is not
   # preloaded.
-  def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}) do
+  def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote) do
     Logger.debug(
       "Object.normalize() called without preloaded object (#{ap_id}).  Consider preloading the object!"
     )
 
     Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
 
-    normalize(ap_id)
+    normalize(ap_id, fetch_remote)
   end
 
-  def normalize(%Activity{data: %{"object" => ap_id}}) do
+  def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote) do
     Logger.debug(
       "Object.normalize() called without preloaded object (#{ap_id}).  Consider preloading the object!"
     )
 
     Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
 
-    normalize(ap_id)
+    normalize(ap_id, fetch_remote)
   end
 
   # Old way, try fetching the object through cache.
-  def normalize(%{"id" => ap_id}), do: normalize(ap_id)
-  def normalize(ap_id) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
-  def normalize(_), do: nil
+  def normalize(%{"id" => ap_id}, fetch_remote), do: normalize(ap_id, fetch_remote)
+  def normalize(ap_id, false) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
+  def normalize(ap_id, true) when is_binary(ap_id), do: Fetcher.fetch_object_from_id!(ap_id)
+  def normalize(_, _), do: nil
 
   # Owned objects can only be mutated by their owner
   def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}),
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
new file mode 100644 (file)
index 0000000..27e89d8
--- /dev/null
@@ -0,0 +1,64 @@
+defmodule Pleroma.Object.Containment do
+  @moduledoc """
+  # Object Containment
+
+  This module contains some useful functions for containing objects to specific
+  origins and determining those origins.  They previously lived in the
+  ActivityPub `Transmogrifier` module.
+
+  Object containment is an important step in validating remote objects to prevent
+  spoofing, therefore removal of object containment functions is NOT recommended.
+  """
+
+  require Logger
+
+  def get_actor(%{"actor" => actor}) when is_binary(actor) do
+    actor
+  end
+
+  def get_actor(%{"actor" => actor}) when is_list(actor) do
+    if is_binary(Enum.at(actor, 0)) do
+      Enum.at(actor, 0)
+    else
+      Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end)
+      |> Map.get("id")
+    end
+  end
+
+  def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do
+    id
+  end
+
+  def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do
+    get_actor(%{"actor" => actor})
+  end
+
+  @doc """
+  Checks that an imported AP object's actor matches the domain it came from.
+  """
+  def contain_origin(_id, %{"actor" => nil}), do: :error
+
+  def contain_origin(id, %{"actor" => _actor} = params) do
+    id_uri = URI.parse(id)
+    actor_uri = URI.parse(get_actor(params))
+
+    if id_uri.host == actor_uri.host do
+      :ok
+    else
+      :error
+    end
+  end
+
+  def contain_origin_from_id(_id, %{"id" => nil}), do: :error
+
+  def contain_origin_from_id(id, %{"id" => other_id} = _params) do
+    id_uri = URI.parse(id)
+    other_uri = URI.parse(other_id)
+
+    if id_uri.host == other_uri.host do
+      :ok
+    else
+      :error
+    end
+  end
+end
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
new file mode 100644 (file)
index 0000000..19d9c51
--- /dev/null
@@ -0,0 +1,78 @@
+defmodule Pleroma.Object.Fetcher do
+  alias Pleroma.Object
+  alias Pleroma.Object.Containment
+  alias Pleroma.Web.ActivityPub.Transmogrifier
+  alias Pleroma.Web.OStatus
+
+  require Logger
+
+  @httpoison Application.get_env(:pleroma, :httpoison)
+
+  # TODO:
+  # This will create a Create activity, which we need internally at the moment.
+  def fetch_object_from_id(id) do
+    if object = Object.get_cached_by_ap_id(id) do
+      {:ok, object}
+    else
+      Logger.info("Fetching #{id} via AP")
+
+      with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
+           nil <- Object.normalize(data, false),
+           params <- %{
+             "type" => "Create",
+             "to" => data["to"],
+             "cc" => data["cc"],
+             "actor" => data["actor"] || data["attributedTo"],
+             "object" => data
+           },
+           :ok <- Containment.contain_origin(id, params),
+           {:ok, activity} <- Transmogrifier.handle_incoming(params) do
+        {:ok, Object.normalize(activity.data["object"], false)}
+      else
+        {:error, {:reject, nil}} ->
+          {:reject, nil}
+
+        object = %Object{} ->
+          {:ok, object}
+
+        _e ->
+          Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
+
+          case OStatus.fetch_activity_from_url(id) do
+            {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"], false)}
+            e -> e
+          end
+      end
+    end
+  end
+
+  def fetch_object_from_id!(id) do
+    with {:ok, object} <- fetch_object_from_id(id) do
+      object
+    else
+      _e ->
+        nil
+    end
+  end
+
+  def fetch_and_contain_remote_object_from_id(id) do
+    Logger.info("Fetching #{id} via AP")
+
+    with true <- String.starts_with?(id, "http"),
+         {:ok, %{body: body, status_code: code}} when code in 200..299 <-
+           @httpoison.get(
+             id,
+             [Accept: "application/activity+json"],
+             follow_redirect: true,
+             timeout: 10000,
+             recv_timeout: 20000
+           ),
+         {:ok, data} <- Jason.decode(body),
+         :ok <- Containment.contain_origin_from_id(id, data) do
+      {:ok, data}
+    else
+      e ->
+        {:error, e}
+    end
+  end
+end
index 54dd4097c755ab99b798a8f11b41915d95d43228..1a3b47cb34fe401d707e6c5e4abbb41785f781bc 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   alias Pleroma.Instances
   alias Pleroma.Notification
   alias Pleroma.Object
+  alias Pleroma.Object.Fetcher
   alias Pleroma.Pagination
   alias Pleroma.Repo
   alias Pleroma.Upload
@@ -14,7 +15,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   alias Pleroma.Web.ActivityPub.MRF
   alias Pleroma.Web.ActivityPub.Transmogrifier
   alias Pleroma.Web.Federator
-  alias Pleroma.Web.OStatus
   alias Pleroma.Web.WebFinger
 
   import Ecto.Query
@@ -121,7 +121,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
          {:ok, map} <- MRF.filter(map),
          {recipients, _, _} = get_recipients(map),
          {:fake, false, map, recipients} <- {:fake, fake, map, recipients},
-         {:ok, object} <- insert_full_object(map) do
+         {:ok, map, object} <- insert_full_object(map) do
       {:ok, activity} =
         Repo.insert(%Activity{
           data: map,
@@ -169,7 +169,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   def stream_out(activity) do
     public = "https://www.w3.org/ns/activitystreams#Public"
 
-    if activity.data["type"] in ["Create", "Announce", "Delete"] do
+    if activity.data["type"] in ["Create", "Announce"] do
+      object = Object.normalize(activity.data["object"])
       Pleroma.Web.Streamer.stream("user", activity)
       Pleroma.Web.Streamer.stream("list", activity)
 
@@ -181,12 +182,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
         end
 
         if activity.data["type"] in ["Create"] do
-          activity.data["object"]
+          object.data
           |> Map.get("tag", [])
           |> Enum.filter(fn tag -> is_bitstring(tag) end)
           |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
 
-          if activity.data["object"]["attachment"] != [] do
+          if object.data["attachment"] != [] do
             Pleroma.Web.Streamer.stream("public:media", activity)
 
             if activity.local do
@@ -859,7 +860,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   def fetch_and_prepare_user_from_ap_id(ap_id) do
-    with {:ok, data} <- fetch_and_contain_remote_object_from_id(ap_id) do
+    with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
       user_data_from_user_object(data)
     else
       e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
@@ -969,60 +970,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  # TODO:
-  # This will create a Create activity, which we need internally at the moment.
-  def fetch_object_from_id(id) do
-    if object = Object.get_cached_by_ap_id(id) do
-      {:ok, object}
-    else
-      with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
-           nil <- Object.normalize(data),
-           params <- %{
-             "type" => "Create",
-             "to" => data["to"],
-             "cc" => data["cc"],
-             "actor" => data["actor"] || data["attributedTo"],
-             "object" => data
-           },
-           :ok <- Transmogrifier.contain_origin(id, params),
-           {:ok, activity} <- Transmogrifier.handle_incoming(params) do
-        {:ok, Object.normalize(activity)}
-      else
-        {:error, {:reject, nil}} ->
-          {:reject, nil}
-
-        object = %Object{} ->
-          {:ok, object}
-
-        _e ->
-          Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
-
-          case OStatus.fetch_activity_from_url(id) do
-            {:ok, [activity | _]} -> {:ok, Object.normalize(activity)}
-            e -> e
-          end
-      end
-    end
-  end
-
-  def fetch_and_contain_remote_object_from_id(id) do
-    Logger.info("Fetching object #{id} via AP")
-
-    with true <- String.starts_with?(id, "http"),
-         {:ok, %{body: body, status: code}} when code in 200..299 <-
-           @httpoison.get(
-             id,
-             [{:Accept, "application/activity+json"}]
-           ),
-         {:ok, data} <- Jason.decode(body),
-         :ok <- Transmogrifier.contain_origin_from_id(id, data) do
-      {:ok, data}
-    else
-      e ->
-        {:error, e}
-    end
-  end
-
   # filter out broken threads
   def contain_broken_threads(%Activity{} = activity, %User{} = user) do
     entire_thread_visible_for_user?(activity, user)
index 3331ebebd3b67406c5ec4e4a57797dd33a62eca5..0b80566bf5be5859ce25ccf54e00a8deb3188c48 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   alias Pleroma.Activity
   alias Pleroma.Object
+  alias Pleroma.Object.Fetcher
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.ObjectView
@@ -173,7 +174,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
       "Signature missing or not from author, relayed Create message, fetching object from source"
     )
 
-    ActivityPub.fetch_object_from_id(params["object"]["id"])
+    Fetcher.fetch_object_from_id(params["object"]["id"])
 
     json(conn, "ok")
   end
index 39cd319212c415b265282dd12ae766907139c5f6..0637b18dc17e07089a2dfbf6cdd13a0e69a38f42 100644 (file)
@@ -6,6 +6,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   @moduledoc """
   A module to handle coding from internal to wire ActivityPub and back.
   """
+  alias Pleroma.User
+  alias Pleroma.Object
+  alias Pleroma.Object.Containment
   alias Pleroma.Activity
   alias Pleroma.Object
   alias Pleroma.Repo
@@ -18,56 +21,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   require Logger
 
-  def get_actor(%{"actor" => actor}) when is_binary(actor) do
-    actor
-  end
-
-  def get_actor(%{"actor" => actor}) when is_list(actor) do
-    if is_binary(Enum.at(actor, 0)) do
-      Enum.at(actor, 0)
-    else
-      Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end)
-      |> Map.get("id")
-    end
-  end
-
-  def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do
-    id
-  end
-
-  def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do
-    get_actor(%{"actor" => actor})
-  end
-
-  @doc """
-  Checks that an imported AP object's actor matches the domain it came from.
-  """
-  def contain_origin(_id, %{"actor" => nil}), do: :error
-
-  def contain_origin(id, %{"actor" => _actor} = params) do
-    id_uri = URI.parse(id)
-    actor_uri = URI.parse(get_actor(params))
-
-    if id_uri.host == actor_uri.host do
-      :ok
-    else
-      :error
-    end
-  end
-
-  def contain_origin_from_id(_id, %{"id" => nil}), do: :error
-
-  def contain_origin_from_id(id, %{"id" => other_id} = _params) do
-    id_uri = URI.parse(id)
-    other_uri = URI.parse(other_id)
-
-    if id_uri.host == other_uri.host do
-      :ok
-    else
-      :error
-    end
-  end
-
   @doc """
   Modifies an incoming AP object (mastodon format) to our internal format.
   """
@@ -188,7 +141,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   def fix_actor(%{"attributedTo" => actor} = object) do
     object
-    |> Map.put("actor", get_actor(%{"actor" => actor}))
+    |> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
   end
 
   # Check for standardisation
@@ -223,7 +176,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           ""
       end
 
-    case fetch_obj_helper(in_reply_to_id) do
+    case get_obj_helper(in_reply_to_id) do
       {:ok, replied_object} ->
         with %Activity{} = _activity <-
                Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
@@ -448,7 +401,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   # - emoji
   def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
       when objtype in ["Article", "Note", "Video", "Page"] do
-    actor = get_actor(data)
+    actor = Containment.get_actor(data)
 
     data =
       Map.put(data, "actor", actor)
@@ -506,7 +459,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def handle_incoming(
         %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data
       ) do
-    with actor <- get_actor(data),
+    with actor <- Containment.get_actor(data),
          %User{} = followed <- User.get_or_fetch_by_ap_id(actor),
          {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
          {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
@@ -532,7 +485,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def handle_incoming(
         %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data
       ) do
-    with actor <- get_actor(data),
+    with actor <- Containment.get_actor(data),
          %User{} = followed <- User.get_or_fetch_by_ap_id(actor),
          {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
          {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
@@ -556,9 +509,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def handle_incoming(
         %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data
       ) do
-    with actor <- get_actor(data),
+    with actor <- Containment.get_actor(data),
          %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
-         {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
+         {:ok, object} <- get_obj_helper(object_id),
          {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do
       {:ok, activity}
     else
@@ -569,9 +522,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   def handle_incoming(
         %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data
       ) do
-    with actor <- get_actor(data),
+    with actor <- Containment.get_actor(data),
          %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
-         {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
+         {:ok, object} <- get_obj_helper(object_id),
          public <- Visibility.is_public?(data),
          {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do
       {:ok, activity}
@@ -624,10 +577,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
       ) do
     object_id = Utils.get_ap_id(object_id)
 
-    with actor <- get_actor(data),
+    with actor <- Containment.get_actor(data),
          %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
-         {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
-         :ok <- contain_origin(actor.ap_id, object.data),
+         {:ok, object} <- get_obj_helper(object_id),
+         :ok <- Containment.contain_origin(actor.ap_id, object.data),
          {:ok, activity} <- ActivityPub.delete(object, false) do
       {:ok, activity}
     else
@@ -643,9 +596,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           "id" => id
         } = data
       ) do
-    with actor <- get_actor(data),
+    with actor <- Containment.get_actor(data),
          %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
-         {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
+         {:ok, object} <- get_obj_helper(object_id),
          {:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do
       {:ok, activity}
     else
@@ -713,9 +666,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           "id" => id
         } = data
       ) do
-    with actor <- get_actor(data),
+    with actor <- Containment.get_actor(data),
          %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
-         {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
+         {:ok, object} <- get_obj_helper(object_id),
          {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
       {:ok, activity}
     else
@@ -725,9 +678,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   def handle_incoming(_), do: :error
 
-  def fetch_obj_helper(id) when is_bitstring(id), do: ActivityPub.fetch_object_from_id(id)
-  def fetch_obj_helper(obj) when is_map(obj), do: ActivityPub.fetch_object_from_id(obj["id"])
-
   def get_obj_helper(id) do
     if object = Object.normalize(id), do: {:ok, object}, else: nil
   end
@@ -764,9 +714,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   #  internal -> Mastodon
   #  """
 
-  def prepare_outgoing(%{"type" => "Create", "object" => object} = data) do
+  def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do
     object =
-      object
+      Object.normalize(object_id).data
       |> prepare_object
 
     data =
@@ -827,7 +777,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
   def maybe_fix_object_url(data) do
     if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do
-      case fetch_obj_helper(data["object"]) do
+      case get_obj_helper(data["object"]) do
         {:ok, relative_object} ->
           if relative_object.data["external_url"] do
             _data =
index ccc9da7c667dc03c39a984d164007aad0d6495da..581b9d1ab3e0f232b4e35e954dd5dbeffb4999a6 100644 (file)
@@ -234,14 +234,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   @doc """
   Inserts a full object if it is contained in an activity.
   """
-  def insert_full_object(%{"object" => %{"type" => type} = object_data})
+  def insert_full_object(%{"object" => %{"type" => type} = object_data} = map)
       when is_map(object_data) and type in @supported_object_types do
     with {:ok, object} <- Object.create(object_data) do
-      {:ok, object}
+      map =
+        map
+        |> Map.put("object", object.data["id"])
+
+      {:ok, map, object}
     end
   end
 
-  def insert_full_object(_), do: {:ok, nil}
+  def insert_full_object(map), do: {:ok, map, nil}
 
   def update_object_in_activities(%{data: %{"id" => id}} = object) do
     # TODO
index 74babdf147c84da900f6ac4bfdd041b9b69e1ed8..9c3daac2c994c49d323f35c60f419fce1e031024 100644 (file)
@@ -125,7 +125,10 @@ defmodule Pleroma.Web.CommonAPI do
         "public"
 
       in_reply_to ->
-        Pleroma.Web.MastodonAPI.StatusView.get_visibility(in_reply_to.data["object"])
+        # XXX: these heuristics should be moved out of MastodonAPI.
+        with %Object{} = object <- Object.normalize(in_reply_to) do
+          Pleroma.Web.MastodonAPI.StatusView.get_visibility(object.data)
+        end
     end
   end
 
index 185292878b6e94f72ee9b5fbeb4730f63185c8b5..7781f16350ab105d5c8ec27e71226224b33f2859 100644 (file)
@@ -226,8 +226,10 @@ defmodule Pleroma.Web.CommonAPI.Utils do
     }
 
     if inReplyTo do
+      inReplyToObject = Object.normalize(inReplyTo.data["object"])
+
       object
-      |> Map.put("inReplyTo", inReplyTo.data["object"]["id"])
+      |> Map.put("inReplyTo", inReplyToObject.data["id"])
     else
       object
     end
index c47328e13a4e096182ffc97ae1688f7c559eb099..a1f6373a45b0b2d9a3a58249ef12976516cc5d3c 100644 (file)
@@ -12,6 +12,7 @@ defmodule Pleroma.Web.Federator do
   alias Pleroma.Web.ActivityPub.Visibility
   alias Pleroma.Web.Federator.RetryQueue
   alias Pleroma.Web.OStatus
+  alias Pleroma.Object.Containment
   alias Pleroma.Web.Salmon
   alias Pleroma.Web.WebFinger
   alias Pleroma.Web.Websub
@@ -136,7 +137,7 @@ defmodule Pleroma.Web.Federator do
     # actor shouldn't be acting on objects outside their own AP server.
     with {:ok, _user} <- ap_enabled_actor(params["actor"]),
          nil <- Activity.normalize(params["id"]),
-         :ok <- Transmogrifier.contain_origin_from_id(params["actor"], params),
+         :ok <- Containment.contain_origin_from_id(params["actor"], params),
          {:ok, activity} <- Transmogrifier.handle_incoming(params) do
       {:ok, activity}
     else
index 63fadce3811ceea8a681860888d49ad8f8f4cb8d..24a2d4cb9cd8db210d8e7918174b1a1adf8aa38b 100644 (file)
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   use Pleroma.Web, :controller
-
+  alias Pleroma.Object.Fetcher
   alias Ecto.Changeset
   alias Pleroma.Activity
   alias Pleroma.Config
@@ -681,7 +681,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def favourited_by(conn, %{"id" => id}) do
-    with %Activity{data: %{"object" => %{"likes" => likes}}} <- Activity.get_by_id(id) do
+    with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
+         %Object{data: %{"likes" => likes}} <- Object.normalize(object) do
       q = from(u in User, where: u.ap_id in ^likes)
       users = Repo.all(q)
 
@@ -694,7 +695,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def reblogged_by(conn, %{"id" => id}) do
-    with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Activity.get_by_id(id) do
+    with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
+         %Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
       q = from(u in User, where: u.ap_id in ^announces)
       users = Repo.all(q)
 
@@ -997,7 +999,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   def status_search(user, query) do
     fetched =
       if Regex.match?(~r/https?:/, query) do
-        with {:ok, object} <- ActivityPub.fetch_object_from_id(query),
+        with {:ok, object} <- Fetcher.fetch_object_from_id(query),
              %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
              true <- Visibility.visible_for_user?(activity, user) do
           [activity]
index a9f607aa5dd8b38d8da006471cc847eb18bf50c6..e4de5ecfb3726b7da261a2a6e022aae53278261a 100644 (file)
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   alias Pleroma.Activity
   alias Pleroma.HTML
   alias Pleroma.Repo
+  alias Pleroma.Object
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.CommonAPI.Utils
@@ -19,8 +20,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   defp get_replied_to_activities(activities) do
     activities
     |> Enum.map(fn
-      %{data: %{"type" => "Create", "object" => %{"inReplyTo" => in_reply_to}}} ->
-        in_reply_to != "" && in_reply_to
+      %{data: %{"type" => "Create", "object" => object}} ->
+        object = Object.normalize(object)
+        object.data["inReplyTo"] != "" && object.data["inReplyTo"]
 
       _ ->
         nil
@@ -29,7 +31,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
     |> Activity.create_by_object_ap_id()
     |> Repo.all()
     |> Enum.reduce(%{}, fn activity, acc ->
-      Map.put(acc, activity.data["object"]["id"], activity)
+      object = Object.normalize(activity.data["object"])
+      Map.put(acc, object.data["id"], activity)
     end)
   end
 
@@ -55,8 +58,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   defp get_context_id(_), do: nil
 
   defp reblogged?(activity, user) do
-    object = activity.data["object"] || %{}
-    present?(user && user.ap_id in (object["announcements"] || []))
+    object = Object.normalize(activity) || %{}
+    present?(user && user.ap_id in (object.data["announcements"] || []))
   end
 
   def render("index.json", opts) do
@@ -123,13 +126,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   end
 
   def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do
+    object = Object.normalize(object)
+
     user = get_user(activity.data["actor"])
 
-    like_count = object["like_count"] || 0
-    announcement_count = object["announcement_count"] || 0
+    like_count = object.data["like_count"] || 0
+    announcement_count = object.data["announcement_count"] || 0
 
-    tags = object["tag"] || []
-    sensitive = object["sensitive"] || Enum.member?(tags, "nsfw")
+    tags = object.data["tag"] || []
+    sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
 
     mentions =
       activity.recipients
@@ -137,15 +142,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       |> Enum.filter(& &1)
       |> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
 
-    favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
-    bookmarked = opts[:for] && object["id"] in opts[:for].bookmarks
+    favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
+
+    bookmarked = opts[:for] && object.data["id"] in opts[:for].bookmarks
 
-    attachment_data = object["attachment"] || []
+    attachment_data = object.data["attachment"] || []
     attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
 
-    created_at = Utils.to_masto_date(object["published"])
+    created_at = Utils.to_masto_date(object.data["published"])
 
     reply_to = get_reply_to(activity, opts)
+
     reply_to_user = reply_to && get_user(reply_to.data["actor"])
 
     content =
@@ -167,7 +174,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         "mastoapi:content"
       )
 
-    summary = object["summary"] || ""
+    summary = object.data["summary"] || ""
 
     summary_html =
       summary
@@ -190,12 +197,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       if user.local do
         Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
       else
-        object["external_url"] || object["id"]
+        object.data["external_url"] || object.data["id"]
       end
 
     %{
       id: to_string(activity.id),
-      uri: object["id"],
+      uri: object.data["id"],
       url: url,
       account: AccountView.render("account.json", %{user: user}),
       in_reply_to_id: reply_to && to_string(reply_to.id),
@@ -205,7 +212,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       content: content_html,
       created_at: created_at,
       reblogs_count: announcement_count,
-      replies_count: object["repliesCount"] || 0,
+      replies_count: object.data["repliesCount"] || 0,
       favourites_count: like_count,
       reblogged: reblogged?(activity, opts[:for]),
       favourited: present?(favorited),
@@ -223,7 +230,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         website: nil
       },
       language: nil,
-      emojis: build_emojis(activity.data["object"]["emoji"]),
+      emojis: build_emojis(object.data["emoji"]),
       pleroma: %{
         local: activity.local,
         conversation_id: get_context_id(activity),
@@ -305,15 +312,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   end
 
   def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
-    with nil <- replied_to_activities[activity.data["object"]["inReplyTo"]] do
+    object = Object.normalize(activity.data["object"])
+
+    with nil <- replied_to_activities[object.data["inReplyTo"]] do
       # If user didn't participate in the thread
       Activity.get_in_reply_to_activity(activity)
     end
   end
 
   def get_reply_to(%{data: %{"object" => object}}, _) do
-    if object["inReplyTo"] && object["inReplyTo"] != "" do
-      Activity.get_create_by_object_ap_id(object["inReplyTo"])
+    object = Object.normalize(object)
+
+    if object.data["inReplyTo"] && object.data["inReplyTo"] != "" do
+      Activity.get_create_by_object_ap_id(object.data["inReplyTo"])
     else
       nil
     end
@@ -321,8 +332,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
 
   def get_visibility(object) do
     public = "https://www.w3.org/ns/activitystreams#Public"
-    to = object["to"] || []
-    cc = object["cc"] || []
+    to = object.data["to"] || []
+    cc = object.data["cc"] || []
 
     cond do
       public in to ->
@@ -343,25 +354,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
     end
   end
 
-  def render_content(%{"type" => "Video"} = object) do
-    with name when not is_nil(name) and name != "" <- object["name"] do
-      "<p><a href=\"#{object["id"]}\">#{name}</a></p>#{object["content"]}"
+  def render_content(%{data: %{"type" => "Video"}} = object) do
+    with name when not is_nil(name) and name != "" <- object.data["name"] do
+      "<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"
     else
-      _ -> object["content"] || ""
+      _ -> object.data["content"] || ""
     end
   end
 
-  def render_content(%{"type" => object_type} = object)
+  def render_content(%{data: %{"type" => object_type}} = object)
       when object_type in ["Article", "Page"] do
-    with summary when not is_nil(summary) and summary != "" <- object["name"],
-         url when is_bitstring(url) <- object["url"] do
-      "<p><a href=\"#{url}\">#{summary}</a></p>#{object["content"]}"
+    with summary when not is_nil(summary) and summary != "" <- object.data["name"],
+         url when is_bitstring(url) <- object.data["url"] do
+      "<p><a href=\"#{url}\">#{summary}</a></p>#{object.data["content"]}"
     else
-      _ -> object["content"] || ""
+      _ -> object.data["content"] || ""
     end
   end
 
-  def render_content(object), do: object["content"] || ""
+  def render_content(object), do: object.data["content"] || ""
 
   @doc """
   Builds a dictionary tags.
index 1a1b74bb0b987b09a26e1a26bc5f18ac3d44c55b..b11a2b5ce3e31bd3431f63b2db972559de918a48 100644 (file)
@@ -54,23 +54,16 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
     end)
   end
 
-  defp get_links(%{local: true, data: data}) do
+  defp get_links(%{local: true}, %{"id" => object_id}) do
     h = fn str -> [to_charlist(str)] end
 
     [
-      {:link, [type: ['application/atom+xml'], href: h.(data["object"]["id"]), rel: 'self'], []},
-      {:link, [type: ['text/html'], href: h.(data["object"]["id"]), rel: 'alternate'], []}
+      {:link, [type: ['application/atom+xml'], href: h.(object_id), rel: 'self'], []},
+      {:link, [type: ['text/html'], href: h.(object_id), rel: 'alternate'], []}
     ]
   end
 
-  defp get_links(%{
-         local: false,
-         data: %{
-           "object" => %{
-             "external_url" => external_url
-           }
-         }
-       }) do
+  defp get_links(%{local: false}, %{"external_url" => external_url}) do
     h = fn str -> [to_charlist(str)] end
 
     [
@@ -78,7 +71,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
     ]
   end
 
-  defp get_links(_activity), do: []
+  defp get_links(_activity, _object_data), do: []
 
   defp get_emoji_links(emojis) do
     Enum.map(emojis, fn {emoji, file} ->
@@ -88,14 +81,16 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
 
   def to_simple_form(activity, user, with_author \\ false)
 
-  def to_simple_form(%{data: %{"object" => %{"type" => "Note"}}} = activity, user, with_author) do
+  def to_simple_form(%{data: %{"type" => "Create"}} = activity, user, with_author) do
     h = fn str -> [to_charlist(str)] end
 
-    updated_at = activity.data["object"]["published"]
-    inserted_at = activity.data["object"]["published"]
+    object = Object.normalize(activity.data["object"])
+
+    updated_at = object.data["published"]
+    inserted_at = object.data["published"]
 
     attachments =
-      Enum.map(activity.data["object"]["attachment"] || [], fn attachment ->
+      Enum.map(object.data["attachment"] || [], fn attachment ->
         url = hd(attachment["url"])
 
         {:link,
@@ -108,7 +103,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
     mentions = activity.recipients |> get_mentions
 
     categories =
-      (activity.data["object"]["tag"] || [])
+      (object.data["tag"] || [])
       |> Enum.map(fn tag ->
         if is_binary(tag) do
           {:category, [term: to_charlist(tag)], []}
@@ -118,11 +113,11 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
       end)
       |> Enum.filter(& &1)
 
-    emoji_links = get_emoji_links(activity.data["object"]["emoji"] || %{})
+    emoji_links = get_emoji_links(object.data["emoji"] || %{})
 
     summary =
-      if activity.data["object"]["summary"] do
-        [{:summary, [], h.(activity.data["object"]["summary"])}]
+      if object.data["summary"] do
+        [{:summary, [], h.(object.data["summary"])}]
       else
         []
       end
@@ -131,10 +126,9 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
       {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},
       {:"activity:verb", ['http://activitystrea.ms/schema/1.0/post']},
       # For notes, federate the object id.
-      {:id, h.(activity.data["object"]["id"])},
+      {:id, h.(object.data["id"])},
       {:title, ['New note by #{user.nickname}']},
-      {:content, [type: 'html'],
-       h.(activity.data["object"]["content"] |> String.replace(~r/[\n\r]/, ""))},
+      {:content, [type: 'html'], h.(object.data["content"] |> String.replace(~r/[\n\r]/, ""))},
       {:published, h.(inserted_at)},
       {:updated, h.(updated_at)},
       {:"ostatus:conversation", [ref: h.(activity.data["context"])],
@@ -142,7 +136,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
       {:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []}
     ] ++
       summary ++
-      get_links(activity) ++
+      get_links(activity, object.data) ++
       categories ++ attachments ++ in_reply_to ++ author ++ mentions ++ emoji_links
   end
 
index db995ec77d2307da309192dc57dcefe9fdbb77a5..ec6e5cfaf3226cdb61e4ba7b1e72d3bba161a532 100644 (file)
@@ -113,8 +113,9 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
          cw <- OStatus.get_cw(entry),
          in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry),
          in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to),
-         in_reply_to <-
-           (in_reply_to_activity && in_reply_to_activity.data["object"]["id"]) || in_reply_to,
+         in_reply_to_object <-
+           (in_reply_to_activity && Object.normalize(in_reply_to_activity)) || nil,
+         in_reply_to <- (in_reply_to_object && in_reply_to_object.data["id"]) || in_reply_to,
          attachments <- OStatus.get_attachments(entry),
          context <- get_context(entry, in_reply_to),
          tags <- OStatus.get_tags(entry),
index ed45ca73574fc62b407ff0429f08beb3f88c107f..9441984c7e1de3ba74559385c2a6e214e2b11f7e 100644 (file)
@@ -75,7 +75,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
 
   def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
     if is_status?(acct) do
-      {:ok, object} = ActivityPub.fetch_object_from_id(acct)
+      {:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct)
       %Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])
       redirect(conn, to: "/notice/#{activity_id}")
     else
@@ -101,7 +101,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
   end
 
   defp is_status?(acct) do
-    case ActivityPub.fetch_and_contain_remote_object_from_id(acct) do
+    case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do
       {:ok, %{"type" => type}} when type in ["Article", "Note", "Video", "Page", "Question"] ->
         true
 
index ecb2b437ba4cff9249284a7ca2da2d6ce6474975..c64152da8f844cde4f5e70dfdee7fba3bb7af7bf 100644 (file)
@@ -224,15 +224,17 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
 
   def render(
         "activity.json",
-        %{activity: %{data: %{"type" => "Create", "object" => object}} = activity} = opts
+        %{activity: %{data: %{"type" => "Create", "object" => object_id}} = activity} = opts
       ) do
     user = get_user(activity.data["actor"], opts)
 
-    created_at = object["published"] |> Utils.date_to_asctime()
-    like_count = object["like_count"] || 0
-    announcement_count = object["announcement_count"] || 0
-    favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
-    repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
+    object = Object.normalize(object_id)
+
+    created_at = object.data["published"] |> Utils.date_to_asctime()
+    like_count = object.data["like_count"] || 0
+    announcement_count = object.data["announcement_count"] || 0
+    favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
+    repeated = opts[:for] && opts[:for].ap_id in (object.data["announcements"] || [])
     pinned = activity.id in user.info.pinned_activities
 
     attentions =
@@ -245,12 +247,12 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
 
     conversation_id = get_context_id(activity, opts)
 
-    tags = activity.data["object"]["tag"] || []
-    possibly_sensitive = activity.data["object"]["sensitive"] || Enum.member?(tags, "nsfw")
+    tags = object.data["tag"] || []
+    possibly_sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")
 
     tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
 
-    {summary, content} = render_content(object)
+    {summary, content} = render_content(object.data)
 
     html =
       content
@@ -259,7 +261,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
         activity,
         "twitterapi:content"
       )
-      |> Formatter.emojify(object["emoji"])
+      |> Formatter.emojify(object.data["emoji"])
 
     text =
       if content do
@@ -284,7 +286,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
 
     %{
       "id" => activity.id,
-      "uri" => activity.data["object"]["id"],
+      "uri" => object.data["id"],
       "user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
       "statusnet_html" => html,
       "text" => text,
@@ -297,20 +299,20 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
       "in_reply_to_ostatus_uri" => reply_user && reply_user.ap_id,
       "in_reply_to_user_id" => reply_user && reply_user.id,
       "statusnet_conversation_id" => conversation_id,
-      "attachments" => (object["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts),
+      "attachments" => (object.data["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts),
       "attentions" => attentions,
       "fave_num" => like_count,
       "repeat_num" => announcement_count,
       "favorited" => !!favorited,
       "repeated" => !!repeated,
       "pinned" => pinned,
-      "external_url" => object["external_url"] || object["id"],
+      "external_url" => object.data["external_url"] || object.data["id"],
       "tags" => tags,
       "activity_type" => "post",
       "possibly_sensitive" => possibly_sensitive,
       "visibility" => StatusView.get_visibility(object),
       "summary" => summary,
-      "summary_html" => summary |> Formatter.emojify(object["emoji"]),
+      "summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
       "card" => card,
       "muted" => CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user)
     }
diff --git a/test/object/containment_test.exs b/test/object/containment_test.exs
new file mode 100644 (file)
index 0000000..268675c
--- /dev/null
@@ -0,0 +1,59 @@
+defmodule Pleroma.Object.ContainmentTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.User
+  alias Pleroma.Object.Containment
+  alias Pleroma.Web.ActivityPub.ActivityPub
+
+  import Pleroma.Factory
+
+  describe "general origin containment" do
+    test "contain_origin_from_id() catches obvious spoofing attempts" do
+      data = %{
+        "id" => "http://example.com/~alyssa/activities/1234.json"
+      }
+
+      :error =
+        Containment.contain_origin_from_id(
+          "http://example.org/~alyssa/activities/1234.json",
+          data
+        )
+    end
+
+    test "contain_origin_from_id() allows alternate IDs within the same origin domain" do
+      data = %{
+        "id" => "http://example.com/~alyssa/activities/1234.json"
+      }
+
+      :ok =
+        Containment.contain_origin_from_id(
+          "http://example.com/~alyssa/activities/1234",
+          data
+        )
+    end
+
+    test "contain_origin_from_id() allows matching IDs" do
+      data = %{
+        "id" => "http://example.com/~alyssa/activities/1234.json"
+      }
+
+      :ok =
+        Containment.contain_origin_from_id(
+          "http://example.com/~alyssa/activities/1234.json",
+          data
+        )
+    end
+
+    test "users cannot be collided through fake direction spoofing attempts" do
+      user =
+        insert(:user, %{
+          nickname: "rye@niu.moe",
+          local: false,
+          ap_id: "https://niu.moe/users/rye",
+          follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
+        })
+
+      {:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye")
+    end
+  end
+end
diff --git a/test/object/fetcher_test.exs b/test/object/fetcher_test.exs
new file mode 100644 (file)
index 0000000..3bbade9
--- /dev/null
@@ -0,0 +1,85 @@
+defmodule Pleroma.Object.FetcherTest do
+  use Pleroma.DataCase
+
+  alias Pleroma.{Activity, Object}
+  alias Pleroma.Object.Fetcher
+
+  import Pleroma.Factory
+
+  describe "actor origin containment" do
+    test "it rejects objects with a bogus origin" do
+      {:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json")
+    end
+
+    test "it rejects objects when attributedTo is wrong (variant 1)" do
+      {:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity2.json")
+    end
+
+    test "it rejects objects when attributedTo is wrong (variant 2)" do
+      {:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity3.json")
+    end
+  end
+
+  describe "fetching an object" do
+    test "it fetches an object" do
+      {:ok, object} =
+        Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
+
+      assert activity = Activity.get_create_activity_by_object_ap_id(object.data["id"])
+      assert activity.data["id"]
+
+      {:ok, object_again} =
+        Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
+
+      assert [attachment] = object.data["attachment"]
+      assert is_list(attachment["url"])
+
+      assert object == object_again
+    end
+
+    test "it works with objects only available via Ostatus" do
+      {:ok, object} = Fetcher.fetch_object_from_id("https://shitposter.club/notice/2827873")
+      assert activity = Activity.get_create_activity_by_object_ap_id(object.data["id"])
+      assert activity.data["id"]
+
+      {:ok, object_again} = Fetcher.fetch_object_from_id("https://shitposter.club/notice/2827873")
+
+      assert object == object_again
+    end
+
+    test "it correctly stitches up conversations between ostatus and ap" do
+      last = "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
+      {:ok, object} = Fetcher.fetch_object_from_id(last)
+
+      object = Object.get_by_ap_id(object.data["inReplyTo"])
+      assert object
+    end
+  end
+
+  describe "implementation quirks" do
+    test "it can fetch plume articles" do
+      {:ok, object} =
+        Fetcher.fetch_object_from_id(
+          "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"
+        )
+
+      assert object
+    end
+
+    test "it can fetch peertube videos" do
+      {:ok, object} =
+        Fetcher.fetch_object_from_id(
+          "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
+        )
+
+      assert object
+    end
+
+    test "all objects with fake directions are rejected by the object fetcher" do
+      {:error, _} =
+        Fetcher.fetch_and_contain_remote_object_from_id(
+          "https://info.pleroma.site/activity4.json"
+        )
+    end
+  end
+end
index 911757d57c601e83f5e54dbd668247d6e818c2ab..a30efd48c7791cfca8c3c66040c2579f74188a39 100644 (file)
@@ -58,4 +58,26 @@ defmodule Pleroma.ObjectTest do
       assert cached_object.data["type"] == "Tombstone"
     end
   end
+
+  describe "normalizer" do
+    test "fetches unknown objects by default" do
+      %Object{} =
+        object = Object.normalize("http://mastodon.example.org/@admin/99541947525187367")
+
+      assert object.data["url"] == "http://mastodon.example.org/@admin/99541947525187367"
+    end
+
+    test "fetches unknown objects when fetch_remote is explicitly true" do
+      %Object{} =
+        object = Object.normalize("http://mastodon.example.org/@admin/99541947525187367", true)
+
+      assert object.data["url"] == "http://mastodon.example.org/@admin/99541947525187367"
+    end
+
+    test "does not fetch unknown objects when fetch_remote is false" do
+      assert is_nil(
+               Object.normalize("http://mastodon.example.org/@admin/99541947525187367", false)
+             )
+    end
+  end
 end
index 17fec05b102e193183d09a3b30453a2cebd86a0d..68bfb3858f7a89f2199da7f47bbf638353cf17b7 100644 (file)
@@ -192,8 +192,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       }
 
       {:ok, %Activity{} = activity} = ActivityPub.insert(data)
-      assert is_binary(activity.data["object"]["id"])
-      assert %Object{} = Object.get_by_ap_id(activity.data["object"]["id"])
+      object = Object.normalize(activity.data["object"])
+
+      assert is_binary(object.data["id"])
+      assert %Object{} = Object.get_by_ap_id(activity.data["object"])
     end
   end
 
@@ -206,7 +208,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
           to: ["user1", "user1", "user2"],
           actor: user,
           context: "",
-          object: %{}
+          object: %{
+            "to" => ["user1", "user1", "user2"],
+            "type" => "Note",
+            "content" => "testing"
+          }
         })
 
       assert activity.data["to"] == ["user1", "user2"]
@@ -635,6 +641,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     end
   end
 
+  describe "fetch the latest Follow" do
+    test "fetches the latest Follow activity" do
+      %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
+      follower = Repo.get_by(User, ap_id: activity.data["actor"])
+      followed = Repo.get_by(User, ap_id: activity.data["object"])
+
+      assert activity == Utils.fetch_latest_follow(follower, followed)
+    end
+  end
+
   describe "fetching an object" do
     test "it fetches an object" do
       {:ok, object} =
@@ -871,15 +887,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     end
   end
 
-  test "it can fetch plume articles" do
-    {:ok, object} =
-      ActivityPub.fetch_object_from_id(
-        "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"
-      )
-
-    assert object
-  end
-
   describe "update" do
     test "it creates an update activity with the new user data" do
       user = insert(:user)
index c857a7ec1e44372f7a818b4454b7cfd413598be7..5559cdf879ffe963179fa246e9c74b6f596f404c 100644 (file)
@@ -50,14 +50,14 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
         |> Map.put("object", object)
 
       {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
+      returned_object = Object.normalize(returned_activity.data["object"])
 
       assert activity =
                Activity.get_create_by_object_ap_id(
                  "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
                )
 
-      assert returned_activity.data["object"]["inReplyToAtomUri"] ==
-               "https://shitposter.club/notice/2827873"
+      assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
     end
 
     test "it works for incoming notices" do
@@ -80,7 +80,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
       assert data["actor"] == "http://mastodon.example.org/users/admin"
 
-      object = data["object"]
+      object = Object.normalize(data["object"]).data
       assert object["id"] == "http://mastodon.example.org/users/admin/statuses/99512778738411822"
 
       assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
@@ -107,7 +107,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
 
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
-      assert Enum.at(data["object"]["tag"], 2) == "moo"
+      object = Object.normalize(data["object"])
+
+      assert Enum.at(object.data["tag"], 2) == "moo"
     end
 
     test "it works for incoming notices with contentMap" do
@@ -115,8 +117,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
         File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
 
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+      object = Object.normalize(data["object"])
 
-      assert data["object"]["content"] ==
+      assert object.data["content"] ==
                "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
     end
 
@@ -124,8 +127,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
 
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+      object = Object.normalize(data["object"])
 
-      assert data["object"]["content"] ==
+      assert object.data["content"] ==
                "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
     end
 
@@ -141,24 +145,27 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
 
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+      object = Object.normalize(data["object"])
 
-      assert data["object"]["emoji"] == %{
+      assert object.data["emoji"] == %{
                "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
              }
 
       data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
 
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+      object = Object.normalize(data["object"])
 
-      assert "test" in data["object"]["tag"]
+      assert "test" in object.data["tag"]
     end
 
     test "it works for incoming notices with url not being a string (prismo)" do
       data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
 
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
+      object = Object.normalize(data["object"])
 
-      assert data["object"]["url"] == "https://prismo.news/posts/83"
+      assert object.data["url"] == "https://prismo.news/posts/83"
     end
 
     test "it cleans up incoming notices which are not really DMs" do
@@ -231,14 +238,14 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       data =
         File.read!("test/fixtures/mastodon-like.json")
         |> Poison.decode!()
-        |> Map.put("object", activity.data["object"]["id"])
+        |> Map.put("object", activity.data["object"])
 
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
 
       assert data["actor"] == "http://mastodon.example.org/users/admin"
       assert data["type"] == "Like"
       assert data["id"] == "http://mastodon.example.org/users/admin#likes/2"
-      assert data["object"] == activity.data["object"]["id"]
+      assert data["object"] == activity.data["object"]
     end
 
     test "it returns an error for incoming unlikes wihout a like activity" do
@@ -248,7 +255,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       data =
         File.read!("test/fixtures/mastodon-undo-like.json")
         |> Poison.decode!()
-        |> Map.put("object", activity.data["object"]["id"])
+        |> Map.put("object", activity.data["object"])
 
       assert Transmogrifier.handle_incoming(data) == :error
     end
@@ -260,7 +267,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       like_data =
         File.read!("test/fixtures/mastodon-like.json")
         |> Poison.decode!()
-        |> Map.put("object", activity.data["object"]["id"])
+        |> Map.put("object", activity.data["object"])
 
       {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
 
@@ -302,7 +309,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       data =
         File.read!("test/fixtures/mastodon-announce.json")
         |> Poison.decode!()
-        |> Map.put("object", activity.data["object"]["id"])
+        |> Map.put("object", activity.data["object"])
 
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
 
@@ -312,7 +319,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       assert data["id"] ==
                "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
 
-      assert data["object"] == activity.data["object"]["id"]
+      assert data["object"] == activity.data["object"]
 
       assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
     end
@@ -450,7 +457,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
       object =
         data["object"]
-        |> Map.put("id", activity.data["object"]["id"])
+        |> Map.put("id", activity.data["object"])
 
       data =
         data
@@ -471,7 +478,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
       object =
         data["object"]
-        |> Map.put("id", activity.data["object"]["id"])
+        |> Map.put("id", activity.data["object"])
 
       data =
         data
@@ -489,7 +496,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       announce_data =
         File.read!("test/fixtures/mastodon-announce.json")
         |> Poison.decode!()
-        |> Map.put("object", activity.data["object"]["id"])
+        |> Map.put("object", activity.data["object"])
 
       {:ok, %Activity{data: announce_data, local: false}} =
         Transmogrifier.handle_incoming(announce_data)
@@ -504,7 +511,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
       assert data["type"] == "Undo"
       assert data["object"]["type"] == "Announce"
-      assert data["object"]["object"] == activity.data["object"]["id"]
+      assert data["object"]["object"] == activity.data["object"]
 
       assert data["object"]["id"] ==
                "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
@@ -1088,10 +1095,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
   end
 
   describe "actor origin containment" do
-    test "it rejects objects with a bogus origin" do
-      {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity.json")
-    end
-
     test "it rejects activities which reference objects with bogus origins" do
       data = %{
         "@context" => "https://www.w3.org/ns/activitystreams",
@@ -1105,10 +1108,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       :error = Transmogrifier.handle_incoming(data)
     end
 
-    test "it rejects objects when attributedTo is wrong (variant 1)" do
-      {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity2.json")
-    end
-
     test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
       data = %{
         "@context" => "https://www.w3.org/ns/activitystreams",
@@ -1122,10 +1121,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       :error = Transmogrifier.handle_incoming(data)
     end
 
-    test "it rejects objects when attributedTo is wrong (variant 2)" do
-      {:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity3.json")
-    end
-
     test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
       data = %{
         "@context" => "https://www.w3.org/ns/activitystreams",
index 34aa5bf1884f949c9a8642ffba4a5dc2c07cb6f6..b9ed088e41c6600f736ed5fa865e2d264df2f356 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPITest do
   use Pleroma.DataCase
   alias Pleroma.Activity
   alias Pleroma.User
+  alias Pleroma.Object
   alias Pleroma.Web.CommonAPI
 
   import Pleroma.Factory
@@ -32,7 +33,9 @@ defmodule Pleroma.Web.CommonAPITest do
     user = insert(:user)
     {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"})
 
-    assert activity.data["object"]["tag"] == ["2hu"]
+    object = Object.normalize(activity.data["object"])
+
+    assert object.data["tag"] == ["2hu"]
   end
 
   test "it adds emoji in the object" do
@@ -64,8 +67,9 @@ defmodule Pleroma.Web.CommonAPITest do
           "content_type" => "text/html"
         })
 
-      content = activity.data["object"]["content"]
-      assert content == "<p><b>2hu</b></p>alert('xss')"
+      object = Object.normalize(activity.data["object"])
+
+      assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
     end
 
     test "it filters out obviously bad tags when accepting a post as Markdown" do
@@ -79,8 +83,9 @@ defmodule Pleroma.Web.CommonAPITest do
           "content_type" => "text/markdown"
         })
 
-      content = activity.data["object"]["content"]
-      assert content == "<p><b>2hu</b></p>alert('xss')"
+      object = Object.normalize(activity.data["object"])
+
+      assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
     end
   end
 
index db2fdc2f6dda92a15df7e0f3020ee301f5c387fe..4ea50c7c698dbeec43d33f7b9287b9e0ea3b703b 100644 (file)
@@ -7,6 +7,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
 
   alias Pleroma.Activity
   alias Pleroma.User
+  alias Pleroma.Repo
+  alias Pleroma.Object
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.CommonAPI.Utils
@@ -53,14 +55,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
 
   test "a note with null content" do
     note = insert(:note_activity)
+    note_object = Object.normalize(note.data["object"])
 
     data =
-      note.data
-      |> put_in(["object", "content"], nil)
+      note_object.data
+      |> Map.put("content", nil)
 
-    note =
-      note
-      |> Map.put(:data, data)
+    Object.change(note_object, %{data: data})
+    |> Repo.update()
 
     User.get_cached_by_ap_id(note.data["actor"])
 
index 9fd100f63bc0131f992d8a5db64423d590cfcb7a..50467c71fbf1d8b49bd7210d0643ca0eae32332b 100644 (file)
@@ -28,34 +28,35 @@ defmodule Pleroma.Web.OStatusTest do
   test "handle incoming note - GS, Salmon" do
     incoming = File.read!("test/fixtures/incoming_note_activity.xml")
     {:ok, [activity]} = OStatus.handle_incoming(incoming)
+    object = Object.normalize(activity.data["object"])
 
     user = User.get_by_ap_id(activity.data["actor"])
     assert user.info.note_count == 1
     assert activity.data["type"] == "Create"
-    assert activity.data["object"]["type"] == "Note"
+    assert object.data["type"] == "Note"
 
-    assert activity.data["object"]["id"] ==
-             "tag:gs.example.org:4040,2017-04-23:noticeId=29:objectType=note"
+    assert object.data["id"] == "tag:gs.example.org:4040,2017-04-23:noticeId=29:objectType=note"
 
     assert activity.data["published"] == "2017-04-23T14:51:03+00:00"
-    assert activity.data["object"]["published"] == "2017-04-23T14:51:03+00:00"
+    assert object.data["published"] == "2017-04-23T14:51:03+00:00"
 
     assert activity.data["context"] ==
              "tag:gs.example.org:4040,2017-04-23:objectType=thread:nonce=f09e22f58abd5c7b"
 
     assert "http://pleroma.example.org:4000/users/lain3" in activity.data["to"]
-    assert activity.data["object"]["emoji"] == %{"marko" => "marko.png", "reimu" => "reimu.png"}
+    assert object.data["emoji"] == %{"marko" => "marko.png", "reimu" => "reimu.png"}
     assert activity.local == false
   end
 
   test "handle incoming notes - GS, subscription" do
     incoming = File.read!("test/fixtures/ostatus_incoming_post.xml")
     {:ok, [activity]} = OStatus.handle_incoming(incoming)
+    object = Object.normalize(activity.data["object"])
 
     assert activity.data["type"] == "Create"
-    assert activity.data["object"]["type"] == "Note"
-    assert activity.data["object"]["actor"] == "https://social.heldscal.la/user/23211"
-    assert activity.data["object"]["content"] == "Will it blend?"
+    assert object.data["type"] == "Note"
+    assert object.data["actor"] == "https://social.heldscal.la/user/23211"
+    assert object.data["content"] == "Will it blend?"
     user = User.get_cached_by_ap_id(activity.data["actor"])
     assert User.ap_followers(user) in activity.data["to"]
     assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
@@ -64,20 +65,22 @@ defmodule Pleroma.Web.OStatusTest do
   test "handle incoming notes with attachments - GS, subscription" do
     incoming = File.read!("test/fixtures/incoming_websub_gnusocial_attachments.xml")
     {:ok, [activity]} = OStatus.handle_incoming(incoming)
+    object = Object.normalize(activity.data["object"])
 
     assert activity.data["type"] == "Create"
-    assert activity.data["object"]["type"] == "Note"
-    assert activity.data["object"]["actor"] == "https://social.heldscal.la/user/23211"
-    assert activity.data["object"]["attachment"] |> length == 2
-    assert activity.data["object"]["external_url"] == "https://social.heldscal.la/notice/2020923"
+    assert object.data["type"] == "Note"
+    assert object.data["actor"] == "https://social.heldscal.la/user/23211"
+    assert object.data["attachment"] |> length == 2
+    assert object.data["external_url"] == "https://social.heldscal.la/notice/2020923"
     assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
   end
 
   test "handle incoming notes with tags" do
     incoming = File.read!("test/fixtures/ostatus_incoming_post_tag.xml")
     {:ok, [activity]} = OStatus.handle_incoming(incoming)
+    object = Object.normalize(activity.data["object"])
 
-    assert activity.data["object"]["tag"] == ["nsfw"]
+    assert object.data["tag"] == ["nsfw"]
     assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
   end
 
@@ -92,10 +95,11 @@ defmodule Pleroma.Web.OStatusTest do
 
     incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml")
     {:ok, [activity]} = OStatus.handle_incoming(incoming)
+    object = Object.normalize(activity.data["object"])
 
     assert activity.data["type"] == "Create"
-    assert activity.data["object"]["type"] == "Note"
-    assert activity.data["object"]["actor"] == "https://mastodon.social/users/lambadalambda"
+    assert object.data["type"] == "Note"
+    assert object.data["actor"] == "https://mastodon.social/users/lambadalambda"
     assert activity.data["context"] == "2hu"
     assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
   end
@@ -103,42 +107,47 @@ defmodule Pleroma.Web.OStatusTest do
   test "handle incoming notes - Mastodon, with CW" do
     incoming = File.read!("test/fixtures/mastodon-note-cw.xml")
     {:ok, [activity]} = OStatus.handle_incoming(incoming)
+    object = Object.normalize(activity.data["object"])
 
     assert activity.data["type"] == "Create"
-    assert activity.data["object"]["type"] == "Note"
-    assert activity.data["object"]["actor"] == "https://mastodon.social/users/lambadalambda"
-    assert activity.data["object"]["summary"] == "technologic"
+    assert object.data["type"] == "Note"
+    assert object.data["actor"] == "https://mastodon.social/users/lambadalambda"
+    assert object.data["summary"] == "technologic"
     assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
   end
 
   test "handle incoming unlisted messages, put public into cc" do
     incoming = File.read!("test/fixtures/mastodon-note-unlisted.xml")
     {:ok, [activity]} = OStatus.handle_incoming(incoming)
+    object = Object.normalize(activity.data["object"])
+
     refute "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
     assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["cc"]
-    refute "https://www.w3.org/ns/activitystreams#Public" in activity.data["object"]["to"]
-    assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["object"]["cc"]
+    refute "https://www.w3.org/ns/activitystreams#Public" in object.data["to"]
+    assert "https://www.w3.org/ns/activitystreams#Public" in object.data["cc"]
   end
 
   test "handle incoming retweets - Mastodon, with CW" do
     incoming = File.read!("test/fixtures/cw_retweet.xml")
     {:ok, [[_activity, retweeted_activity]]} = OStatus.handle_incoming(incoming)
+    retweeted_object = Object.normalize(retweeted_activity.data["object"])
 
-    assert retweeted_activity.data["object"]["summary"] == "Hey."
+    assert retweeted_object.data["summary"] == "Hey."
   end
 
   test "handle incoming notes - GS, subscription, reply" do
     incoming = File.read!("test/fixtures/ostatus_incoming_reply.xml")
     {:ok, [activity]} = OStatus.handle_incoming(incoming)
+    object = Object.normalize(activity.data["object"])
 
     assert activity.data["type"] == "Create"
-    assert activity.data["object"]["type"] == "Note"
-    assert activity.data["object"]["actor"] == "https://social.heldscal.la/user/23211"
+    assert object.data["type"] == "Note"
+    assert object.data["actor"] == "https://social.heldscal.la/user/23211"
 
-    assert activity.data["object"]["content"] ==
+    assert object.data["content"] ==
              "@<a href=\"https://gs.archae.me/user/4687\" class=\"h-card u-url p-nickname mention\" title=\"shpbot\">shpbot</a> why not indeed."
 
-    assert activity.data["object"]["inReplyTo"] ==
+    assert object.data["inReplyTo"] ==
              "tag:gs.archae.me,2017-04-30:noticeId=778260:objectType=note"
 
     assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
@@ -150,17 +159,18 @@ defmodule Pleroma.Web.OStatusTest do
 
     assert activity.data["type"] == "Announce"
     assert activity.data["actor"] == "https://social.heldscal.la/user/23211"
-    assert activity.data["object"] == retweeted_activity.data["object"]["id"]
+    assert activity.data["object"] == retweeted_activity.data["object"]
     assert "https://pleroma.soykaf.com/users/lain" in activity.data["to"]
     refute activity.local
 
     retweeted_activity = Activity.get_by_id(retweeted_activity.id)
+    retweeted_object = Object.normalize(retweeted_activity.data["object"])
     assert retweeted_activity.data["type"] == "Create"
     assert retweeted_activity.data["actor"] == "https://pleroma.soykaf.com/users/lain"
     refute retweeted_activity.local
-    assert retweeted_activity.data["object"]["announcement_count"] == 1
-    assert String.contains?(retweeted_activity.data["object"]["content"], "mastodon")
-    refute String.contains?(retweeted_activity.data["object"]["content"], "Test account")
+    assert retweeted_object.data["announcement_count"] == 1
+    assert String.contains?(retweeted_object.data["content"], "mastodon")
+    refute String.contains?(retweeted_object.data["content"], "Test account")
   end
 
   test "handle incoming retweets - GS, subscription - local message" do
@@ -192,10 +202,11 @@ defmodule Pleroma.Web.OStatusTest do
   test "handle incoming retweets - Mastodon, salmon" do
     incoming = File.read!("test/fixtures/share.xml")
     {:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming)
+    retweeted_object = Object.normalize(retweeted_activity.data["object"])
 
     assert activity.data["type"] == "Announce"
     assert activity.data["actor"] == "https://mastodon.social/users/lambadalambda"
-    assert activity.data["object"] == retweeted_activity.data["object"]["id"]
+    assert activity.data["object"] == retweeted_activity.data["object"]
 
     assert activity.data["id"] ==
              "tag:mastodon.social,2017-05-03:objectId=4934452:objectType=Status"
@@ -204,7 +215,7 @@ defmodule Pleroma.Web.OStatusTest do
     assert retweeted_activity.data["type"] == "Create"
     assert retweeted_activity.data["actor"] == "https://pleroma.soykaf.com/users/lain"
     refute retweeted_activity.local
-    refute String.contains?(retweeted_activity.data["object"]["content"], "Test account")
+    refute String.contains?(retweeted_object.data["content"], "Test account")
   end
 
   test "handle incoming favorites - GS, websub" do
@@ -214,7 +225,7 @@ defmodule Pleroma.Web.OStatusTest do
 
       assert activity.data["type"] == "Like"
       assert activity.data["actor"] == "https://social.heldscal.la/user/23211"
-      assert activity.data["object"] == favorited_activity.data["object"]["id"]
+      assert activity.data["object"] == favorited_activity.data["object"]
 
       assert activity.data["id"] ==
                "tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061643:2017-05-05T09:12:50+00:00"
@@ -223,7 +234,7 @@ defmodule Pleroma.Web.OStatusTest do
       assert favorited_activity.data["type"] == "Create"
       assert favorited_activity.data["actor"] == "https://shitposter.club/user/1"
 
-      assert favorited_activity.data["object"]["id"] ==
+      assert favorited_activity.data["object"] ==
                "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
 
       refute favorited_activity.local
@@ -258,17 +269,17 @@ defmodule Pleroma.Web.OStatusTest do
   test "handle incoming replies" do
     incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml")
     {:ok, [activity]} = OStatus.handle_incoming(incoming)
+    object = Object.normalize(activity.data["object"])
 
     assert activity.data["type"] == "Create"
-    assert activity.data["object"]["type"] == "Note"
+    assert object.data["type"] == "Note"
 
-    assert activity.data["object"]["inReplyTo"] ==
+    assert object.data["inReplyTo"] ==
              "http://pleroma.example.org:4000/objects/55bce8fc-b423-46b1-af71-3759ab4670bc"
 
     assert "http://pleroma.example.org:4000/users/lain5" in activity.data["to"]
 
-    assert activity.data["object"]["id"] ==
-             "tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note"
+    assert object.data["id"] == "tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note"
 
     assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
   end
@@ -495,7 +506,7 @@ defmodule Pleroma.Web.OStatusTest do
 
         assert activity.data["actor"] == "https://shitposter.club/user/1"
 
-        assert activity.data["object"]["id"] ==
+        assert activity.data["object"] ==
                  "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
       end)
     end
@@ -504,7 +515,7 @@ defmodule Pleroma.Web.OStatusTest do
       url = "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056"
       {:ok, [activity]} = OStatus.fetch_activity_from_url(url)
       assert activity.data["actor"] == "https://social.sakamoto.gq/users/eal"
-      assert activity.data["object"]["id"] == url
+      assert activity.data["object"] == url
     end
   end
 
index 4c9ae2da8033e24c502537ad38ecbab652fc62af..5bea1037ad10643e6df28a40a74034efac8d6d71 100644 (file)
@@ -46,13 +46,14 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
     }
 
     {:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input)
+    object = Object.normalize(activity.data["object"])
 
     expected_text =
       "Hello again, <span class='h-card'><a data-user='#{mentioned_user.id}' class='u-url mention' href='shp'>@<span>shp</span></a></span>.&lt;script&gt;&lt;/script&gt;<br>This is on another :moominmamma: line. <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a> <a class='hashtag' data-tag='epic' href='http://localhost:4001/tag/epic' rel='tag'>#epic</a> <a class='hashtag' data-tag='phantasmagoric' href='http://localhost:4001/tag/phantasmagoric' rel='tag'>#phantasmagoric</a><br><a href=\"http://example.org/image.jpg\" class='attachment'>image.jpg</a>"
 
-    assert get_in(activity.data, ["object", "content"]) == expected_text
-    assert get_in(activity.data, ["object", "type"]) == "Note"
-    assert get_in(activity.data, ["object", "actor"]) == user.ap_id
+    assert get_in(object.data, ["content"]) == expected_text
+    assert get_in(object.data, ["type"]) == "Note"
+    assert get_in(object.data, ["actor"]) == user.ap_id
     assert get_in(activity.data, ["actor"]) == user.ap_id
     assert Enum.member?(get_in(activity.data, ["cc"]), User.ap_followers(user))
 
@@ -65,18 +66,18 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
     assert activity.local == true
 
     assert %{"moominmamma" => "http://localhost:4001/finmoji/128px/moominmamma-128.png"} =
-             activity.data["object"]["emoji"]
+             object.data["emoji"]
 
     # hashtags
-    assert activity.data["object"]["tag"] == ["2hu", "epic", "phantasmagoric"]
+    assert object.data["tag"] == ["2hu", "epic", "phantasmagoric"]
 
     # Add a context
     assert is_binary(get_in(activity.data, ["context"]))
-    assert is_binary(get_in(activity.data, ["object", "context"]))
+    assert is_binary(get_in(object.data, ["context"]))
 
-    assert is_list(activity.data["object"]["attachment"])
+    assert is_list(object.data["attachment"])
 
-    assert activity.data["object"] == Object.get_by_ap_id(activity.data["object"]["id"]).data
+    assert activity.data["object"] == object.data["id"]
 
     user = User.get_by_ap_id(user.ap_id)
 
@@ -91,6 +92,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
     }
 
     {:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input)
+    object = Object.normalize(activity.data["object"])
 
     input = %{
       "status" => "Here's your (you).",
@@ -98,13 +100,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
     }
 
     {:ok, reply = %Activity{}} = TwitterAPI.create_status(user, input)
+    reply_object = Object.normalize(reply.data["object"])
 
     assert get_in(reply.data, ["context"]) == get_in(activity.data, ["context"])
 
-    assert get_in(reply.data, ["object", "context"]) ==
-             get_in(activity.data, ["object", "context"])
+    assert get_in(reply_object.data, ["context"]) == get_in(object.data, ["context"])
 
-    assert get_in(reply.data, ["object", "inReplyTo"]) == get_in(activity.data, ["object", "id"])
+    assert get_in(reply_object.data, ["inReplyTo"]) == get_in(activity.data, ["object"])
     assert Activity.get_in_reply_to_activity(reply).id == activity.id
   end
 
index ee9a0c834ed6eef0f5105d488a289b1203edd3cb..7ef0270cc01583124057b4d94dffdc9927d7f528 100644 (file)
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
   use Pleroma.DataCase
 
   alias Pleroma.Activity
+  alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
@@ -125,10 +126,11 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
     other_user = insert(:user, %{nickname: "shp"})
 
     {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
+    object = Object.normalize(activity.data["object"])
 
     result = ActivityView.render("activity.json", activity: activity)
 
-    convo_id = Utils.context_to_conversation_id(activity.data["object"]["context"])
+    convo_id = Utils.context_to_conversation_id(object.data["context"])
 
     expected = %{
       "activity_type" => "post",
@@ -136,8 +138,8 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
       "attentions" => [
         UserView.render("show.json", %{user: other_user})
       ],
-      "created_at" => activity.data["object"]["published"] |> Utils.date_to_asctime(),
-      "external_url" => activity.data["object"]["id"],
+      "created_at" => object.data["published"] |> Utils.date_to_asctime(),
+      "external_url" => object.data["id"],
       "fave_num" => 0,
       "favorited" => false,
       "id" => activity.id,
@@ -161,7 +163,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
         }\">@<span>shp</span></a></span>!",
       "tags" => [],
       "text" => "Hey @shp!",
-      "uri" => activity.data["object"]["id"],
+      "uri" => object.data["id"],
       "user" => UserView.render("show.json", %{user: user}),
       "visibility" => "direct",
       "card" => nil,
@@ -175,8 +177,9 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
     user = insert(:user)
     other_user = insert(:user, %{nickname: "shp"})
     {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})
+    object = Object.normalize(activity.data["object"])
 
-    convo_id = Utils.context_to_conversation_id(activity.data["object"]["context"])
+    convo_id = Utils.context_to_conversation_id(object.data["context"])
 
     mocks = [
       {
@@ -277,9 +280,9 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
     other_user = insert(:user, %{nickname: "shp"})
 
     {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})
-    {:ok, announce, _object} = CommonAPI.repeat(activity.id, other_user)
+    {:ok, announce, object} = CommonAPI.repeat(activity.id, other_user)
 
-    convo_id = Utils.context_to_conversation_id(activity.data["object"]["context"])
+    convo_id = Utils.context_to_conversation_id(object.data["context"])
 
     activity = Activity.get_by_id(activity.id)