diff options
Diffstat (limited to 'lib/pleroma/web/activity_pub')
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub.ex | 90 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub_controller.ex | 2 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/mrf.ex | 24 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/mrf/drop_policy.ex | 2 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/mrf/noop_policy.ex | 3 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/mrf/simple_policy.ex | 2 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/transmogrifier.ex | 51 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/utils.ex | 81 |
8 files changed, 244 insertions, 11 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 4ce2e6052..f07e26afd 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1,6 +1,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.{Activity, Repo, Object, Upload, User, Notification} - alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.ActivityPub.{Transmogrifier, MRF} alias Pleroma.Web.WebFinger alias Pleroma.Web.Federator alias Pleroma.Web.OStatus @@ -11,16 +11,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do @httpoison Application.get_env(:pleroma, :httpoison) @instance Application.get_env(:pleroma, :instance) - @rewrite_policy Keyword.get(@instance, :rewrite_policy) def get_recipients(data) do (data["to"] || []) ++ (data["cc"] || []) end + defp check_actor_is_active(actor) do + if not is_nil(actor) do + with user <- User.get_cached_by_ap_id(actor), + nil <- user.info["deactivated"] do + :ok + else + _e -> :reject + end + else + :ok + end + end + def insert(map, local \\ true) when is_map(map) do with nil <- Activity.get_by_ap_id(map["id"]), map <- lazy_put_activity_defaults(map), - {:ok, map} <- @rewrite_policy.filter(map), + :ok <- check_actor_is_active(map["actor"]), + {:ok, map} <- MRF.filter(map), :ok <- insert_full_object(map) do {:ok, activity} = Repo.insert(%Activity{ @@ -127,11 +140,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - def unlike(%User{} = actor, %Object{} = object) do - with %Activity{} = activity <- get_existing_like(actor.ap_id, object), - {:ok, _activity} <- Repo.delete(activity), - {:ok, object} <- remove_like_from_object(activity, object) do - {:ok, object} + def unlike( + %User{} = actor, + %Object{} = object, + activity_id \\ nil, + local \\ true + ) do + with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object), + unlike_data <- make_unlike_data(actor, like_activity, activity_id), + {:ok, unlike_activity} <- insert(unlike_data, local), + {:ok, _activity} <- Repo.delete(like_activity), + {:ok, object} <- remove_like_from_object(like_activity, object), + :ok <- maybe_federate(unlike_activity) do + {:ok, unlike_activity, like_activity, object} else _e -> {:ok, object} end @@ -154,6 +175,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + def unannounce( + %User{} = actor, + %Object{} = object, + activity_id \\ nil, + local \\ true + ) do + with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object), + unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id), + {:ok, unannounce_activity} <- insert(unannounce_data, local), + :ok <- maybe_federate(unannounce_activity), + {:ok, _activity} <- Repo.delete(announce_activity), + {:ok, object} <- remove_announce_from_object(announce_activity, object) do + {:ok, unannounce_activity, announce_activity, object} + else + _e -> {:ok, object} + end + end + def follow(follower, followed, activity_id \\ nil, local \\ true) do with data <- make_follow_data(follower, followed, activity_id), {:ok, activity} <- insert(data, local), @@ -221,11 +260,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Repo.all(query) end - # TODO: Make this work properly with unlisted. def fetch_public_activities(opts \\ %{}) do q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts) q + |> restrict_unlisted() |> Repo.all() |> Enum.reverse() end @@ -254,6 +293,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_visibility(query, _visibility), do: query + def fetch_user_activities(user, reading_user, params \\ %{}) do + params = + params + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("actor_id", user.ap_id) + |> Map.put("whole_db", true) + + recipients = + if reading_user do + ["https://www.w3.org/ns/activitystreams#Public"] ++ + [reading_user.ap_id | reading_user.following] + else + ["https://www.w3.org/ns/activitystreams#Public"] + end + + fetch_activities(recipients, params) + |> Enum.reverse() + end + defp restrict_since(query, %{"since_id" => since_id}) do from(activity in query, where: activity.id > ^since_id) end @@ -356,6 +414,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_blocked(query, _), do: query + defp restrict_unlisted(query) do + from( + activity in query, + where: + fragment( + "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)", + activity.data, + ^["https://www.w3.org/ns/activitystreams#Public"] + ) + ) + end + def fetch_activities_query(recipients, opts \\ %{}) do base_query = from( @@ -406,6 +476,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do "url" => [%{"href" => data["image"]["url"]}] } + data = Transmogrifier.maybe_fix_user_object(data) + user_data = %{ ap_id: data["id"], info: %{ diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 80aae4f0f..c7d50893f 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -93,7 +93,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do Logger.info("Signature not from author, relayed message, fetching from source") ActivityPub.fetch_object_from_id(params["object"]["id"]) else - Logger.info("Signature error") + Logger.info("Signature error - make sure you are forwarding the HTTP Host header!") Logger.info("Could not validate #{params["actor"]}") Logger.info(inspect(conn.req_headers)) end diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex new file mode 100644 index 000000000..0a4e2bf80 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -0,0 +1,24 @@ +defmodule Pleroma.Web.ActivityPub.MRF do + @callback filter(Map.t()) :: {:ok | :reject, Map.t()} + + def filter(object) do + get_policies() + |> Enum.reduce({:ok, object}, fn + policy, {:ok, object} -> + policy.filter(object) + + _, error -> + error + end) + end + + def get_policies() do + Application.get_env(:pleroma, :instance, []) + |> Keyword.get(:rewrite_policy, []) + |> get_policies() + end + + defp get_policies(policy) when is_atom(policy), do: [policy] + defp get_policies(policies) when is_list(policies), do: policies + defp get_policies(_), do: [] +end diff --git a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex index 4333bca28..811947943 100644 --- a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex @@ -1,6 +1,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do require Logger + @behaviour Pleroma.Web.ActivityPub.MRF + @impl true def filter(object) do Logger.info("REJECTING #{inspect(object)}") {:reject, object} diff --git a/lib/pleroma/web/activity_pub/mrf/noop_policy.ex b/lib/pleroma/web/activity_pub/mrf/noop_policy.ex index 9dd3acb04..e26f60d26 100644 --- a/lib/pleroma/web/activity_pub/mrf/noop_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/noop_policy.ex @@ -1,4 +1,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do + @behaviour Pleroma.Web.ActivityPub.MRF + + @impl true def filter(object) do {:ok, object} end diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index d840d759d..8d770387d 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -1,5 +1,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do alias Pleroma.User + @behaviour Pleroma.Web.ActivityPub.MRF @mrf_policy Application.get_env(:pleroma, :mrf_simple) @@ -69,6 +70,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do end end + @impl true def filter(object) do actor_info = URI.parse(object["actor"]) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 5f86f67cd..a31452a63 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -223,9 +223,45 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end + def handle_incoming( + %{ + "type" => "Undo", + "object" => %{"type" => "Announce", "object" => object_id}, + "actor" => actor, + "id" => id + } = data + ) do + with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), + {:ok, object} <- + get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), + {:ok, activity, _, _} <- ActivityPub.unannounce(actor, object, id, false) do + {:ok, activity} + else + e -> :error + end + end + + def handle_incoming( + %{ + "type" => "Undo", + "object" => %{"type" => "Like", "object" => object_id}, + "actor" => actor, + "id" => id + } = data + ) do + with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), + {:ok, object} <- + get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), + {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do + {:ok, activity} + else + e -> :error + end + end + # TODO # Accept - # Undo + # Undo for non-Announce def handle_incoming(_), do: :error @@ -477,4 +513,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do Repo.delete_all(q) end end + + def maybe_fix_user_url(data) do + if is_map(data["url"]) do + data = Map.put(data, "url", data["url"]["href"]) + end + + data + end + + def maybe_fix_user_object(data) do + data + |> maybe_fix_user_url + end end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 7b2bf8fa7..937f032c3 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -5,6 +5,22 @@ defmodule Pleroma.Web.ActivityPub.Utils do alias Ecto.{Changeset, UUID} import Ecto.Query + # Some implementations send the actor URI as the actor field, others send the entire actor object, + # so figure out what the actor's URI is based on what we have. + def normalize_actor(actor) do + cond do + is_binary(actor) -> + actor + + is_map(actor) -> + actor["id"] + end + end + + def normalize_params(params) do + Map.put(params, "actor", normalize_actor(params["actor"])) + end + def make_json_ld_header do %{ "@context" => [ @@ -238,6 +254,28 @@ defmodule Pleroma.Web.ActivityPub.Utils do #### Announce-related helpers @doc """ + Retruns an existing announce activity if the notice has already been announced + """ + def get_existing_announce(actor, %{data: %{"id" => id}}) do + query = + from( + activity in Activity, + where: fragment("(?)->>'actor' = ?", activity.data, ^actor), + # this is to use the index + where: + fragment( + "coalesce((?)->'object'->>'id', (?)->>'object') = ?", + activity.data, + activity.data, + ^id + ), + where: fragment("(?)->>'type' = 'Announce'", activity.data) + ) + + Repo.one(query) + end + + @doc """ Make announce activity data for the given actor and object """ def make_announce_data( @@ -257,12 +295,55 @@ defmodule Pleroma.Web.ActivityPub.Utils do if activity_id, do: Map.put(data, "id", activity_id), else: data end + @doc """ + Make unannounce activity data for the given actor and object + """ + def make_unannounce_data( + %User{ap_id: ap_id} = user, + %Activity{data: %{"context" => context}} = activity, + activity_id + ) do + data = %{ + "type" => "Undo", + "actor" => ap_id, + "object" => activity.data, + "to" => [user.follower_address, activity.data["actor"]], + "cc" => ["https://www.w3.org/ns/activitystreams#Public"], + "context" => context + } + + if activity_id, do: Map.put(data, "id", activity_id), else: data + end + + def make_unlike_data( + %User{ap_id: ap_id} = user, + %Activity{data: %{"context" => context}} = activity, + activity_id + ) do + data = %{ + "type" => "Undo", + "actor" => ap_id, + "object" => activity.data, + "to" => [user.follower_address, activity.data["actor"]], + "cc" => ["https://www.w3.org/ns/activitystreams#Public"], + "context" => context + } + + if activity_id, do: Map.put(data, "id", activity_id), else: data + end + def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do update_element_in_object("announcement", announcements, object) end end + def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do + with announcements <- (object.data["announcements"] || []) |> List.delete(actor) do + update_element_in_object("announcement", announcements, object) + end + end + #### Unfollow-related helpers def make_unfollow_data(follower, followed, follow_activity) do |