diff options
Diffstat (limited to 'lib/pleroma/web/activity_pub')
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub.ex | 166 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub_controller.ex | 61 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/mrf/normalize_markup.ex | 23 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/mrf/reject_non_public.ex | 71 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/mrf/simple_policy.ex | 123 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/mrf/user_allowlist.ex | 23 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/relay.ex | 46 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/transmogrifier.ex | 249 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/utils.ex | 112 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/views/object_view.ex | 43 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/views/user_view.ex | 32 |
11 files changed, 719 insertions, 230 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index cb14f6a57..76c15cf21 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -10,16 +10,39 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do @httpoison Application.get_env(:pleroma, :httpoison) - @instance Application.get_env(:pleroma, :instance) + # For Announce activities, we filter the recipients based on following status for any actors + # that match actual users. See issue #164 for more information about why this is necessary. + defp get_recipients(%{"type" => "Announce"} = data) do + to = data["to"] || [] + cc = data["cc"] || [] + recipients = to ++ cc + actor = User.get_cached_by_ap_id(data["actor"]) + + recipients + |> Enum.filter(fn recipient -> + case User.get_cached_by_ap_id(recipient) do + nil -> + true + + user -> + User.following?(user, actor) + end + end) + + {recipients, to, cc} + end - def get_recipients(data) do - (data["to"] || []) ++ (data["cc"] || []) + defp get_recipients(data) do + to = data["to"] || [] + cc = data["cc"] || [] + recipients = to ++ cc + {recipients, to, 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 + false <- !!user.info["deactivated"] do :ok else _e -> :reject @@ -35,12 +58,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do :ok <- check_actor_is_active(map["actor"]), {:ok, map} <- MRF.filter(map), :ok <- insert_full_object(map) do + {recipients, _, _} = get_recipients(map) + {:ok, activity} = Repo.insert(%Activity{ data: map, local: local, actor: map["actor"], - recipients: get_recipients(map) + recipients: recipients }) Notification.create_notifications(activity) @@ -66,6 +91,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Pleroma.Web.Streamer.stream("public:local", activity) end + activity.data["object"] + |> Map.get("tag", []) + |> Enum.filter(fn tag -> is_bitstring(tag) end) + |> Enum.map(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end) + if activity.data["object"]["attachment"] != [] do Pleroma.Web.Streamer.stream("public:media", activity) @@ -241,8 +271,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do "to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"] } - with Repo.delete(object), - Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)), + with {:ok, _} <- Object.delete(object), {:ok, activity} <- insert(data, local), :ok <- maybe_federate(activity), {:ok, _actor} <- User.decrease_note_count(user) do @@ -381,6 +410,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_tag(query, _), do: query + defp restrict_to_cc(query, recipients_to, recipients_cc) do + from( + activity in query, + where: + fragment( + "(?->'to' \\?| ?) or (?->'cc' \\?| ?)", + activity.data, + ^recipients_to, + activity.data, + ^recipients_cc + ) + ) + end + defp restrict_recipients(query, [], _user), do: query defp restrict_recipients(query, recipients, nil) do @@ -522,9 +565,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Enum.reverse() end - def upload(file) do - data = Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media]) - Repo.insert(%Object{data: data}) + def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do + fetch_activities_query([], opts) + |> restrict_to_cc(recipients_to, recipients_cc) + |> Repo.all() + |> Enum.reverse() + end + + def upload(file, opts \\ []) do + with {:ok, data} <- Upload.store(file, opts) do + Repo.insert(%Object{data: data}) + end end def user_data_from_user_object(data) do @@ -554,19 +605,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do "locked" => locked }, avatar: avatar, - nickname: "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}", name: data["name"], follower_address: data["followers"], bio: data["summary"] } + # nickname can be nil because of virtual actors + user_data = + if data["preferredUsername"] do + Map.put( + user_data, + :nickname, + "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}" + ) + else + Map.put(user_data, :nickname, nil) + end + {:ok, user_data} end def fetch_and_prepare_user_from_ap_id(ap_id) do - with {:ok, %{status_code: 200, body: body}} <- - @httpoison.get(ap_id, [Accept: "application/activity+json"], follow_redirect: true), - {:ok, data} <- Jason.decode(body) do + with {:ok, data} <- 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)}") @@ -593,14 +653,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - @quarantined_instances Keyword.get(@instance, :quarantined_instances, []) - def should_federate?(inbox, public) do if public do true else inbox_info = URI.parse(inbox) - inbox_info.host not in @quarantined_instances + !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host) end end @@ -619,7 +677,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do (Pleroma.Web.Salmon.remote_users(activity) ++ followers) |> Enum.filter(fn user -> User.ap_enabled?(user) end) |> Enum.map(fn %{info: %{"source_data" => data}} -> - (data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"] + (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"] end) |> Enum.uniq() |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) @@ -670,27 +728,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do else 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), + 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["attributedTo"], + "actor" => data["actor"] || data["attributedTo"], "object" => data }, + :ok <- Transmogrifier.contain_origin(id, params), {:ok, activity} <- Transmogrifier.handle_incoming(params) do {:ok, Object.normalize(activity.data["object"])} else + {:error, {:reject, nil}} -> + {:reject, nil} + object = %Object{} -> {:ok, object} @@ -705,6 +758,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do 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 <- Transmogrifier.contain_origin_from_id(id, data) do + {:ok, data} + else + e -> + {:error, e} + end + end + def is_public?(activity) do "https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++ (activity.data["cc"] || [])) @@ -719,4 +793,38 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do y = activity.data["to"] ++ (activity.data["cc"] || []) visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y)) end + + # guard + def entire_thread_visible_for_user?(nil, user), do: false + + # child + def entire_thread_visible_for_user?( + %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail, + user + ) + when is_binary(parent_id) do + parent = Activity.get_in_reply_to_activity(tail) + visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user) + end + + # root + def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user) + + # filter out broken threads + def contain_broken_threads(%Activity{} = activity, %User{} = user) do + entire_thread_visible_for_user?(activity, user) + end + + # do post-processing on a specific activity + def contain_activity(%Activity{} = activity, %User{} = user) do + contain_broken_threads(activity, user) + end + + # do post-processing on a timeline + def contain_timeline(timeline, user) do + timeline + |> Enum.filter(fn activity -> + contain_activity(activity, user) + end) + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index d337532d0..3570a75cb 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -3,12 +3,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.{User, Object} alias Pleroma.Web.ActivityPub.{ObjectView, UserView} alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Relay + alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Federator require Logger action_fallback(:errors) + plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay]) + plug(:relay_active? when action in [:relay]) + + def relay_active?(conn, _) do + if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do + conn + else + conn + |> put_status(404) + |> json(%{error: "not found"}) + |> halt + end + end + def user(conn, %{"nickname" => nickname}) do with %User{} = user <- User.get_cached_by_nickname(nickname), {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do @@ -86,25 +102,54 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do outbox(conn, %{"nickname" => nickname, "max_id" => nil}) end - # TODO: Ensure that this inbox is a recipient of the message + def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do + with %User{} = user <- User.get_cached_by_nickname(nickname), + true <- Utils.recipient_in_message(user.ap_id, params), + params <- Utils.maybe_splice_recipient(user.ap_id, params) do + Federator.enqueue(:incoming_ap_doc, params) + json(conn, "ok") + end + end + def inbox(%{assigns: %{valid_signature: true}} = conn, params) do Federator.enqueue(:incoming_ap_doc, params) json(conn, "ok") end + # only accept relayed Creates + def inbox(conn, %{"type" => "Create"} = params) do + Logger.info( + "Signature missing or not from author, relayed Create message, fetching object from source" + ) + + ActivityPub.fetch_object_from_id(params["object"]["id"]) + + json(conn, "ok") + end + def inbox(conn, params) do headers = Enum.into(conn.req_headers, %{}) - if !String.contains?(headers["signature"] || "", params["actor"]) 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 - make sure you are forwarding the HTTP Host header!") - Logger.info("Could not validate #{params["actor"]}") + if String.contains?(headers["signature"], params["actor"]) do + Logger.info( + "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!" + ) + Logger.info(inspect(conn.req_headers)) end - json(conn, "ok") + json(conn, "error") + end + + def relay(conn, params) do + with %User{} = user <- Relay.get_actor(), + {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do + conn + |> put_resp_header("content-type", "application/activity+json") + |> json(UserView.render("user.json", %{user: user})) + else + nil -> {:error, :not_found} + end end def errors(conn, {:error, :not_found}) do diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex new file mode 100644 index 000000000..c53cb1ad2 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex @@ -0,0 +1,23 @@ +defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do + alias Pleroma.HTML + + @behaviour Pleroma.Web.ActivityPub.MRF + + def filter(%{"type" => activity_type} = object) when activity_type == "Create" do + scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy]) + + child = object["object"] + + content = + child["content"] + |> HTML.filter_tags(scrub_policy) + + child = Map.put(child, "content", content) + + object = Map.put(object, "object", child) + + {:ok, object} + end + + def filter(object), do: {:ok, object} +end diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex index b6936fe90..627284083 100644 --- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex +++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex @@ -2,48 +2,45 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do alias Pleroma.User @behaviour Pleroma.Web.ActivityPub.MRF - @mrf_rejectnonpublic Application.get_env(:pleroma, :mrf_rejectnonpublic) - @allow_followersonly Keyword.get(@mrf_rejectnonpublic, :allow_followersonly) - @allow_direct Keyword.get(@mrf_rejectnonpublic, :allow_direct) - @impl true - def filter(object) do - if object["type"] == "Create" do - user = User.get_cached_by_ap_id(object["actor"]) - public = "https://www.w3.org/ns/activitystreams#Public" - - # Determine visibility - visibility = - cond do - public in object["to"] -> "public" - public in object["cc"] -> "unlisted" - user.follower_address in object["to"] -> "followers" - true -> "direct" - end + def filter(%{"type" => "Create"} = object) do + user = User.get_cached_by_ap_id(object["actor"]) + public = "https://www.w3.org/ns/activitystreams#Public" - case visibility do - "public" -> - {:ok, object} + # Determine visibility + visibility = + cond do + public in object["to"] -> "public" + public in object["cc"] -> "unlisted" + user.follower_address in object["to"] -> "followers" + true -> "direct" + end + + policy = Pleroma.Config.get(:mrf_rejectnonpublic) + + case visibility do + "public" -> + {:ok, object} + + "unlisted" -> + {:ok, object} - "unlisted" -> + "followers" -> + with true <- Keyword.get(policy, :allow_followersonly) do {:ok, object} + else + _e -> {:reject, nil} + end - "followers" -> - with true <- @allow_followersonly do - {:ok, object} - else - _e -> {:reject, nil} - end - - "direct" -> - with true <- @allow_direct do - {:ok, object} - else - _e -> {:reject, nil} - end - end - else - {:ok, object} + "direct" -> + with true <- Keyword.get(policy, :allow_direct) do + {:ok, object} + else + _e -> {:reject, nil} + end end end + + @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 7fecb8a4f..86dcf5080 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -2,81 +2,92 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do alias Pleroma.User @behaviour Pleroma.Web.ActivityPub.MRF - @mrf_policy Application.get_env(:pleroma, :mrf_simple) + defp check_accept(%{host: actor_host} = _actor_info, object) do + accepts = Pleroma.Config.get([:mrf_simple, :accept]) - @accept Keyword.get(@mrf_policy, :accept) - defp check_accept(actor_info, object) do - if length(@accept) > 0 and not (actor_info.host in @accept) do - {:reject, nil} - else - {:ok, object} + cond do + accepts == [] -> {:ok, object} + actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object} + Enum.member?(accepts, actor_host) -> {:ok, object} + true -> {:reject, nil} end end - @reject Keyword.get(@mrf_policy, :reject) - defp check_reject(actor_info, object) do - if actor_info.host in @reject do + defp check_reject(%{host: actor_host} = _actor_info, object) do + if Enum.member?(Pleroma.Config.get([:mrf_simple, :reject]), actor_host) do {:reject, nil} else {:ok, object} end end - @media_removal Keyword.get(@mrf_policy, :media_removal) - defp check_media_removal(actor_info, object) do - if actor_info.host in @media_removal do - child_object = Map.delete(object["object"], "attachment") - object = Map.put(object, "object", child_object) - {:ok, object} - else - {:ok, object} - end + defp check_media_removal( + %{host: actor_host} = _actor_info, + %{"type" => "Create", "object" => %{"attachement" => child_attachment}} = object + ) + when length(child_attachment) > 0 do + object = + if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_removal]), actor_host) do + child_object = Map.delete(object["object"], "attachment") + Map.put(object, "object", child_object) + else + object + end + + {:ok, object} end - @media_nsfw Keyword.get(@mrf_policy, :media_nsfw) - defp check_media_nsfw(actor_info, object) do - child_object = object["object"] + defp check_media_removal(_actor_info, object), do: {:ok, object} - if actor_info.host in @media_nsfw and child_object["attachment"] != nil and - length(child_object["attachment"]) > 0 do - tags = (child_object["tag"] || []) ++ ["nsfw"] - child_object = Map.put(child_object, "tags", tags) - child_object = Map.put(child_object, "sensitive", true) - object = Map.put(object, "object", child_object) - {:ok, object} - else - {:ok, object} - end + defp check_media_nsfw( + %{host: actor_host} = _actor_info, + %{ + "type" => "Create", + "object" => %{"attachment" => child_attachment} = child_object + } = object + ) + when length(child_attachment) > 0 do + object = + if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do + tags = (child_object["tag"] || []) ++ ["nsfw"] + child_object = Map.put(child_object, "tags", tags) + child_object = Map.put(child_object, "sensitive", true) + Map.put(object, "object", child_object) + else + object + end + + {:ok, object} end - @ftl_removal Keyword.get(@mrf_policy, :federated_timeline_removal) - defp check_ftl_removal(actor_info, object) do - if actor_info.host in @ftl_removal do - user = User.get_by_ap_id(object["actor"]) + defp check_media_nsfw(_actor_info, object), do: {:ok, object} - # flip to/cc relationship to make the post unlisted - object = - if "https://www.w3.org/ns/activitystreams#Public" in object["to"] and - user.follower_address in object["cc"] do - to = - List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++ - [user.follower_address] + defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do + object = + with true <- + Enum.member?( + Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]), + actor_host + ), + user <- User.get_cached_by_ap_id(object["actor"]), + true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"], + true <- user.follower_address in object["cc"] do + to = + List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++ + [user.follower_address] - cc = - List.delete(object["cc"], user.follower_address) ++ - ["https://www.w3.org/ns/activitystreams#Public"] + cc = + List.delete(object["cc"], user.follower_address) ++ + ["https://www.w3.org/ns/activitystreams#Public"] - object - |> Map.put("to", to) - |> Map.put("cc", cc) - else - object - end + object + |> Map.put("to", to) + |> Map.put("cc", cc) + else + _ -> object + end - {:ok, object} - else - {:ok, object} - end + {:ok, object} end @impl true diff --git a/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex b/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex new file mode 100644 index 000000000..3503d8692 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex @@ -0,0 +1,23 @@ +defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do + alias Pleroma.Config + + @behaviour Pleroma.Web.ActivityPub.MRF + + defp filter_by_list(object, []), do: {:ok, object} + + defp filter_by_list(%{"actor" => actor} = object, allow_list) do + if actor in allow_list do + {:ok, object} + else + {:reject, nil} + end + end + + @impl true + def filter(object) do + actor_info = URI.parse(object["actor"]) + allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], []) + + filter_by_list(object, allow_list) + end +end diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex new file mode 100644 index 000000000..fcdc6b1c0 --- /dev/null +++ b/lib/pleroma/web/activity_pub/relay.ex @@ -0,0 +1,46 @@ +defmodule Pleroma.Web.ActivityPub.Relay do + alias Pleroma.{User, Object, Activity} + alias Pleroma.Web.ActivityPub.ActivityPub + require Logger + + def get_actor do + User.get_or_create_instance_user() + end + + def follow(target_instance) do + with %User{} = local_user <- get_actor(), + %User{} = target_user <- User.get_or_fetch_by_ap_id(target_instance), + {:ok, activity} <- ActivityPub.follow(local_user, target_user) do + Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}") + {:ok, activity} + else + e -> + Logger.error("error: #{inspect(e)}") + {:error, e} + end + end + + def unfollow(target_instance) do + with %User{} = local_user <- get_actor(), + %User{} = target_user <- User.get_or_fetch_by_ap_id(target_instance), + {:ok, activity} <- ActivityPub.unfollow(local_user, target_user) do + Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}") + {:ok, activity} + else + e -> + Logger.error("error: #{inspect(e)}") + {:error, e} + end + end + + def publish(%Activity{data: %{"type" => "Create"}} = activity) do + with %User{} = user <- get_actor(), + %Object{} = object <- Object.normalize(activity.data["object"]["id"]) do + ActivityPub.announce(user, object) + else + e -> Logger.error("error: #{inspect(e)}") + end + end + + def publish(_), do: nil +end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 1367bc7e3..5864855b0 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -21,13 +21,46 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do if is_binary(Enum.at(actor, 0)) do Enum.at(actor, 0) else - Enum.find(actor, fn %{"type" => type} -> type == "Person" end) + Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end) |> Map.get("id") end end - def get_actor(%{"actor" => actor}) when is_map(actor) do - actor["id"] + 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 """ @@ -37,6 +70,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do object |> fix_actor |> fix_attachments + |> fix_url |> fix_context |> fix_in_reply_to |> fix_emoji @@ -82,9 +116,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do object end - def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object) - when not is_nil(in_reply_to_id) do - case ActivityPub.fetch_object_from_id(in_reply_to_id) do + def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object) + when not is_nil(in_reply_to) do + in_reply_to_id = + cond do + is_bitstring(in_reply_to) -> + in_reply_to + + is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) -> + in_reply_to["id"] + + is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) -> + Enum.at(in_reply_to, 0) + + # Maybe I should output an error too? + true -> + "" + end + + case fetch_obj_helper(in_reply_to_id) do {:ok, replied_object} -> with %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(replied_object.data["id"]) do @@ -96,12 +146,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Map.put("context", replied_object.data["context"] || object["conversation"]) else e -> - Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}") + Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") object end e -> - Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}") + Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") object end end @@ -116,9 +166,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Map.put("conversation", context) end - def fix_attachments(object) do + def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do attachments = - (object["attachment"] || []) + attachment |> Enum.map(fn data -> url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}] Map.put(data, "url", url) @@ -128,21 +178,41 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Map.put("attachment", attachments) end - def fix_emoji(object) do - tags = object["tag"] || [] + def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do + Map.put(object, "attachment", [attachment]) + |> fix_attachments() + end + + def fix_attachments(object), do: object + + def fix_url(%{"url" => url} = object) when is_map(url) do + object + |> Map.put("url", url["href"]) + end + + def fix_url(%{"url" => url} = object) when is_list(url) do + first_element = Enum.at(url, 0) + + url_string = + cond do + is_bitstring(first_element) -> first_element + is_map(first_element) -> first_element["href"] || "" + true -> "" + end + + object + |> Map.put("url", url_string) + end + + def fix_url(object), do: object + + def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do emoji = tags |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end) emoji = emoji |> Enum.reduce(%{}, fn data, mapping -> - name = data["name"] - - name = - if String.starts_with?(name, ":") do - name |> String.slice(1..-2) - else - name - end + name = String.trim(data["name"], ":") mapping |> Map.put(name, data["icon"]["url"]) end) @@ -154,18 +224,37 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Map.put("emoji", emoji) end - def fix_tag(object) do + def fix_emoji(%{"tag" => %{"type" => "Emoji"} = tag} = object) do + name = String.trim(tag["name"], ":") + emoji = %{name => tag["icon"]["url"]} + + object + |> Map.put("emoji", emoji) + end + + def fix_emoji(object), do: object + + def fix_tag(%{"tag" => tag} = object) when is_list(tag) do tags = - (object["tag"] || []) + tag |> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end) |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end) - combined = (object["tag"] || []) ++ tags + combined = tag ++ tags object |> Map.put("tag", combined) end + def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do + combined = [tag, String.slice(hashtag, 1..-1)] + + object + |> Map.put("tag", combined) + end + + def fix_tag(object), do: object + # content map usually only has one language so this will do for now. def fix_content_map(%{"contentMap" => content_map} = object) do content_groups = Map.to_list(content_map) @@ -187,7 +276,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do # - tags # - emoji def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data) - when objtype in ["Article", "Note", "Video"] do + when objtype in ["Article", "Note", "Video", "Page"] do actor = get_actor(data) data = @@ -271,8 +360,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming( %{"type" => "Accept", "object" => follow_object, "actor" => actor, "id" => id} = data ) do - with %User{} = followed <- User.get_or_fetch_by_ap_id(actor), + with actor <- 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"), %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), {:ok, activity} <- ActivityPub.accept(%{ @@ -295,8 +386,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming( %{"type" => "Reject", "object" => follow_object, "actor" => actor, "id" => id} = data ) do - with %User{} = followed <- User.get_or_fetch_by_ap_id(actor), + with actor <- 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"), %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), {:ok, activity} <- ActivityPub.accept(%{ @@ -315,11 +408,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming( - %{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = _data + %{"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), + with actor <- 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, activity, _object} <- ActivityPub.like(actor, object, id, false) do {:ok, activity} else @@ -328,11 +421,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming( - %{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = _data + %{"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), + with actor <- 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, activity, _object} <- ActivityPub.announce(actor, object, id, false) do {:ok, activity} else @@ -341,9 +434,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming( - %{"type" => "Update", "object" => %{"type" => "Person"} = object, "actor" => actor_id} = + %{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} = data - ) do + ) + when object_type in ["Person", "Application", "Service", "Organization"] do with %User{ap_id: ^actor_id} = actor <- User.get_by_ap_id(object["id"]) do {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object) @@ -373,15 +467,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end - # TODO: Make secure. + # TODO: We presently assume that any actor on the same origin domain as the object being + # deleted has the rights to delete that object. A better way to validate whether or not + # the object should be deleted is to refetch the object URI, which should return either + # an error or a tombstone. This would allow us to verify that a deletion actually took + # place. def handle_incoming( - %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = _data + %{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data ) do object_id = Utils.get_ap_id(object_id) - 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), + with actor <- 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, activity} <- ActivityPub.delete(object, false) do {:ok, activity} else @@ -395,11 +494,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do "object" => %{"type" => "Announce", "object" => object_id}, "actor" => actor, "id" => id - } = _data + } = 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), + with actor <- 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, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do {:ok, activity} else @@ -425,9 +524,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end - @ap_config Application.get_env(:pleroma, :activitypub) - @accept_blocks Keyword.get(@ap_config, :accept_blocks) - def handle_incoming( %{ "type" => "Undo", @@ -436,7 +532,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do "id" => id } = _data ) do - with true <- @accept_blocks, + with true <- Pleroma.Config.get([:activitypub, :accept_blocks]), %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked), %User{} = blocker <- User.get_or_fetch_by_ap_id(blocker), {:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do @@ -450,7 +546,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming( %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = data ) do - with true <- @accept_blocks, + with true <- Pleroma.Config.get([:activitypub, :accept_blocks]), %User{local: true} = blocked = User.get_cached_by_ap_id(blocked), %User{} = blocker = User.get_or_fetch_by_ap_id(blocker), {:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do @@ -468,11 +564,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do "object" => %{"type" => "Like", "object" => object_id}, "actor" => actor, "id" => id - } = _data + } = 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), + with actor <- 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, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do {:ok, activity} else @@ -482,6 +578,9 @@ 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 @@ -508,6 +607,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> prepare_attachments |> set_conversation |> set_reply_to_uri + |> strip_internal_fields + |> strip_internal_tags end # @doc @@ -523,7 +624,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do data = data |> Map.put("object", object) - |> Map.put("@context", "https://www.w3.org/ns/activitystreams") + |> Map.merge(Utils.make_json_ld_header()) {:ok, data} end @@ -542,7 +643,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do data = data |> Map.put("object", object) - |> Map.put("@context", "https://www.w3.org/ns/activitystreams") + |> Map.merge(Utils.make_json_ld_header()) {:ok, data} end @@ -560,7 +661,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do data = data |> Map.put("object", object) - |> Map.put("@context", "https://www.w3.org/ns/activitystreams") + |> Map.merge(Utils.make_json_ld_header()) {:ok, data} end @@ -570,14 +671,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do data = data |> maybe_fix_object_url - |> Map.put("@context", "https://www.w3.org/ns/activitystreams") + |> Map.merge(Utils.make_json_ld_header()) {:ok, data} end def maybe_fix_object_url(data) do if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do - case ActivityPub.fetch_object_from_id(data["object"]) do + case fetch_obj_helper(data["object"]) do {:ok, relative_object} -> if relative_object.data["external_url"] do _data = @@ -612,12 +713,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def add_mention_tags(object) do - recipients = object["to"] ++ (object["cc"] || []) - mentions = - recipients - |> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end) - |> Enum.filter(& &1) + object + |> Utils.get_notified_from_object() |> Enum.map(fn user -> %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"} end) @@ -677,6 +775,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Map.put("attachment", attachments) end + defp strip_internal_fields(object) do + object + |> Map.drop([ + "likes", + "like_count", + "announcements", + "announcement_count", + "emoji", + "context_id" + ]) + end + + defp strip_internal_tags(%{"tag" => tags} = object) do + tags = + tags + |> Enum.filter(fn x -> is_map(x) end) + + object + |> Map.put("tag", tags) + end + + defp strip_internal_tags(object), do: object + defp user_upgrade_task(user) do old_follower_address = User.ap_followers(user) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 7cdc1656b..549148989 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -1,11 +1,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do - alias Pleroma.{Repo, Web, Object, Activity, User} + alias Pleroma.{Repo, Web, Object, Activity, User, Notification} alias Pleroma.Web.Router.Helpers alias Pleroma.Web.Endpoint alias Ecto.{Changeset, UUID} import Ecto.Query require Logger + @supported_object_types ["Article", "Note", "Video", "Page"] + # 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 get_ap_id(object) do @@ -19,22 +21,58 @@ defmodule Pleroma.Web.ActivityPub.Utils do Map.put(params, "actor", get_ap_id(params["actor"])) end + defp recipient_in_collection(ap_id, coll) when is_binary(coll), do: ap_id == coll + defp recipient_in_collection(ap_id, coll) when is_list(coll), do: ap_id in coll + defp recipient_in_collection(_, _), do: false + + def recipient_in_message(ap_id, params) do + cond do + recipient_in_collection(ap_id, params["to"]) -> + true + + recipient_in_collection(ap_id, params["cc"]) -> + true + + recipient_in_collection(ap_id, params["bto"]) -> + true + + recipient_in_collection(ap_id, params["bcc"]) -> + true + + # if the message is unaddressed at all, then assume it is directly addressed + # to the recipient + !params["to"] && !params["cc"] && !params["bto"] && !params["bcc"] -> + true + + true -> + false + end + end + + defp extract_list(target) when is_binary(target), do: [target] + defp extract_list(lst) when is_list(lst), do: lst + defp extract_list(_), do: [] + + def maybe_splice_recipient(ap_id, params) do + need_splice = + !recipient_in_collection(ap_id, params["to"]) && + !recipient_in_collection(ap_id, params["cc"]) + + cc_list = extract_list(params["cc"]) + + if need_splice do + params + |> Map.put("cc", [ap_id | cc_list]) + else + params + end + end + def make_json_ld_header do %{ "@context" => [ "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - %{ - "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers", - "sensitive" => "as:sensitive", - "Hashtag" => "as:Hashtag", - "ostatus" => "http://ostatus.org#", - "atomUri" => "ostatus:atomUri", - "inReplyToAtomUri" => "ostatus:inReplyToAtomUri", - "conversation" => "ostatus:conversation", - "toot" => "http://joinmastodon.org/ns#", - "Emoji" => "toot:Emoji" - } + "#{Web.base_url()}/schemas/litepub-0.1.jsonld" ] } end @@ -59,6 +97,21 @@ defmodule Pleroma.Web.ActivityPub.Utils do "#{Web.base_url()}/#{type}/#{UUID.generate()}" end + def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do + fake_create_activity = %{ + "to" => object["to"], + "cc" => object["cc"], + "type" => "Create", + "object" => object + } + + Notification.get_notified_from_activity(%Activity{data: fake_create_activity}, false) + end + + def get_notified_from_object(object) do + Notification.get_notified_from_activity(%Activity{data: object}, false) + end + def create_context(context) do context = context || generate_id("contexts") changeset = Object.context_mapping(context) @@ -128,7 +181,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do Inserts a full object if it is contained in an activity. """ def insert_full_object(%{"object" => %{"type" => type} = object_data}) - when is_map(object_data) and type in ["Article", "Note", "Video"] do + when is_map(object_data) and type in @supported_object_types do with {:ok, _} <- Object.create(object_data) do :ok end @@ -247,11 +300,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do "actor" => follower_id, "to" => [followed_id], "cc" => ["https://www.w3.org/ns/activitystreams#Public"], - "object" => followed_id + "object" => followed_id, + "state" => "pending" } data = if activity_id, do: Map.put(data, "id", activity_id), else: data - data = if User.locked?(followed), do: Map.put(data, "state", "pending"), else: data data end @@ -306,6 +359,24 @@ defmodule Pleroma.Web.ActivityPub.Utils do @doc """ Make announce activity data for the given actor and object """ + # for relayed messages, we only want to send to subscribers + def make_announce_data( + %User{ap_id: ap_id, nickname: nil} = user, + %Object{data: %{"id" => id}} = object, + activity_id + ) do + data = %{ + "type" => "Announce", + "actor" => ap_id, + "object" => id, + "to" => [user.follower_address], + "cc" => [], + "context" => object.data["context"] + } + + if activity_id, do: Map.put(data, "id", activity_id), else: data + end + def make_announce_data( %User{ap_id: ap_id} = user, %Object{data: %{"id" => id}} = object, @@ -360,7 +431,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do if activity_id, do: Map.put(data, "id", activity_id), else: data end - def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do + def add_announce_to_object( + %Activity{ + data: %{"actor" => actor, "cc" => ["https://www.w3.org/ns/activitystreams#Public"]} + }, + object + ) do announcements = if is_list(object.data["announcements"]), do: object.data["announcements"], else: [] @@ -369,6 +445,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do end end + def add_announce_to_object(_, object), do: {:ok, object} + def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do announcements = if is_list(object.data["announcements"]), do: object.data["announcements"], else: [] diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex index cc0b0556b..ff664636c 100644 --- a/lib/pleroma/web/activity_pub/views/object_view.ex +++ b/lib/pleroma/web/activity_pub/views/object_view.ex @@ -1,27 +1,34 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do use Pleroma.Web, :view + alias Pleroma.{Object, Activity} alias Pleroma.Web.ActivityPub.Transmogrifier - def render("object.json", %{object: object}) do - base = %{ - "@context" => [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - %{ - "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers", - "sensitive" => "as:sensitive", - "Hashtag" => "as:Hashtag", - "ostatus" => "http://ostatus.org#", - "atomUri" => "ostatus:atomUri", - "inReplyToAtomUri" => "ostatus:inReplyToAtomUri", - "conversation" => "ostatus:conversation", - "toot" => "http://joinmastodon.org/ns#", - "Emoji" => "toot:Emoji" - } - ] - } + def render("object.json", %{object: %Object{} = object}) do + base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header() additional = Transmogrifier.prepare_object(object.data) Map.merge(base, additional) end + + def render("object.json", %{object: %Activity{data: %{"type" => "Create"}} = activity}) do + base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header() + object = Object.normalize(activity.data["object"]) + + additional = + Transmogrifier.prepare_object(activity.data) + |> Map.put("object", Transmogrifier.prepare_object(object.data)) + + Map.merge(base, additional) + end + + def render("object.json", %{object: %Activity{} = activity}) do + base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header() + object = Object.normalize(activity.data["object"]) + + additional = + Transmogrifier.prepare_object(activity.data) + |> Map.put("object", object.data["id"]) + + Map.merge(base, additional) + end end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 0b1d5a9fa..eb335813d 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -9,6 +9,35 @@ defmodule Pleroma.Web.ActivityPub.UserView do alias Pleroma.Web.ActivityPub.Utils import Ecto.Query + # the instance itself is not a Person, but instead an Application + def render("user.json", %{user: %{nickname: nil} = user}) do + {:ok, user} = WebFinger.ensure_keys_present(user) + {:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"]) + public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) + public_key = :public_key.pem_encode([public_key]) + + %{ + "id" => user.ap_id, + "type" => "Application", + "following" => "#{user.ap_id}/following", + "followers" => "#{user.ap_id}/followers", + "inbox" => "#{user.ap_id}/inbox", + "name" => "Pleroma", + "summary" => "Virtual actor for Pleroma relay", + "url" => user.ap_id, + "manuallyApprovesFollowers" => false, + "publicKey" => %{ + "id" => "#{user.ap_id}#main-key", + "owner" => user.ap_id, + "publicKeyPem" => public_key + }, + "endpoints" => %{ + "sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox" + } + } + |> Map.merge(Utils.make_json_ld_header()) + end + def render("user.json", %{user: user}) do {:ok, user} = WebFinger.ensure_keys_present(user) {:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"]) @@ -42,7 +71,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do "image" => %{ "type" => "Image", "url" => User.banner_url(user) - } + }, + "tag" => user.info["source_data"]["tag"] || [] } |> Map.merge(Utils.make_json_ld_header()) end |