diff options
-rw-r--r-- | lib/pleroma/list.ex | 32 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub.ex | 57 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/publisher.ex | 63 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/transmogrifier.ex | 5 | ||||
-rw-r--r-- | lib/pleroma/web/common_api/common_api.ex | 34 | ||||
-rw-r--r-- | lib/pleroma/web/common_api/utils.ex | 8 | ||||
-rw-r--r-- | lib/pleroma/web/salmon/salmon.ex | 25 |
7 files changed, 175 insertions, 49 deletions
diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex index a5b1cad68..81b842e9c 100644 --- a/lib/pleroma/list.ex +++ b/lib/pleroma/list.ex @@ -12,6 +12,8 @@ defmodule Pleroma.List do alias Pleroma.Repo alias Pleroma.User + @ap_id_regex ~r/^\/users\/(?<nickname>\w+)\/lists\/(?<list_id>\d+)/ + schema "lists" do belongs_to(:user, User, type: Pleroma.FlakeId) field(:title, :string) @@ -32,6 +34,12 @@ defmodule Pleroma.List do |> validate_required([:following]) end + def ap_id(%User{nickname: nickname}, list_id) do + Pleroma.Web.Endpoint.url() <> "/users/#{nickname}/lists/#{list_id}" + end + + def ap_id({nickname, list_id}), do: ap_id(%User{nickname: nickname}, list_id) + def for_user(user, _opts) do query = from( @@ -55,6 +63,19 @@ defmodule Pleroma.List do Repo.one(query) end + def get_by_ap_id(ap_id) do + host = Pleroma.Web.Endpoint.host() + + with %{host: ^host, path: path} <- URI.parse(ap_id), + %{"list_id" => list_id, "nickname" => nickname} <- + Regex.named_captures(@ap_id_regex, path), + %User{} = user <- User.get_cached_by_nickname(nickname) do + get(list_id, user) + else + _ -> nil + end + end + def get_following(%Pleroma.List{following: following} = _list) do q = from( @@ -125,4 +146,15 @@ defmodule Pleroma.List do |> follow_changeset(attrs) |> Repo.update() end + + def memberships(%User{follower_address: follower_address}) do + Pleroma.List + |> where([l], ^follower_address in l.following) + |> join(:inner, [l], u in User, on: l.user_id == u.id) + |> select([l, u], {u.nickname, l.id}) + |> Repo.all() + |> Enum.map(&ap_id/1) + end + + def memberships(_), do: [] end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 11777c220..eba8f8018 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -25,19 +25,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do # 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"] || [] + to = Map.get(data, "to", []) + cc = Map.get(data, "cc", []) + bcc = Map.get(data, "bcc", []) actor = User.get_cached_by_ap_id(data["actor"]) recipients = - (to ++ cc) - |> Enum.filter(fn recipient -> + Enum.filter(Enum.concat([to, cc, bcc]), fn recipient -> case User.get_cached_by_ap_id(recipient) do - nil -> - true - - user -> - User.following?(user, actor) + nil -> true + user -> User.following?(user, actor) end end) @@ -45,17 +42,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end defp get_recipients(%{"type" => "Create"} = data) do - to = data["to"] || [] - cc = data["cc"] || [] - actor = data["actor"] || [] - recipients = (to ++ cc ++ [actor]) |> Enum.uniq() + to = Map.get(data, "to", []) + cc = Map.get(data, "cc", []) + bcc = Map.get(data, "bcc", []) + actor = Map.get(data, "actor", []) + recipients = [to, cc, bcc, [actor]] |> Enum.concat() |> Enum.uniq() {recipients, to, cc} end defp get_recipients(data) do - to = data["to"] || [] - cc = data["cc"] || [] - recipients = to ++ cc + to = Map.get(data, "to", []) + cc = Map.get(data, "cc", []) + bcc = Map.get(data, "bcc", []) + recipients = Enum.concat([to, cc, bcc]) {recipients, to, cc} end @@ -831,9 +830,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp maybe_order(query, _), do: query def fetch_activities_query(recipients, opts \\ %{}) do - base_query = from(activity in Activity) - - base_query + Activity |> maybe_preload_objects(opts) |> maybe_preload_bookmarks(opts) |> maybe_order(opts) @@ -857,9 +854,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def fetch_activities(recipients, opts \\ %{}) do - fetch_activities_query(recipients, opts) + list_memberships = Pleroma.List.memberships(opts["user"]) + + fetch_activities_query(recipients ++ list_memberships, opts) |> Pagination.fetch_paginated(opts) |> Enum.reverse() + |> maybe_update_cc(list_memberships, opts["user"]) + end + + defp maybe_update_cc(activities, [], _), do: activities + defp maybe_update_cc(activities, _, nil), do: activities + + defp maybe_update_cc(activities, list_memberships, user) do + Enum.map(activities, fn + %{data: %{"bcc" => bcc}} = activity when is_list(bcc) -> + if Enum.any?(bcc, &(&1 in list_memberships)) do + update_in(activity.data["cc"], &[user.ap_id | &1]) + else + activity + end + + activity -> + activity + end) end def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 8e3af0a81..036ac892e 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -93,18 +93,67 @@ defmodule Pleroma.Web.ActivityPub.Publisher do end end - @doc """ - Publishes an activity to all relevant peers. - """ - def publish(%User{} = actor, %Activity{} = activity) do - remote_followers = + defp recipients(actor, activity) do + followers = if actor.follower_address in activity.recipients do {:ok, followers} = User.get_followers(actor) - followers |> Enum.filter(&(!&1.local)) + Enum.filter(followers, &(!&1.local)) else [] end + Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers + end + + defp get_cc_ap_ids(ap_id, recipients) do + host = Map.get(URI.parse(ap_id), :host) + + recipients + |> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end) + |> Enum.map(& &1.ap_id) + end + + @doc """ + Publishes an activity with BCC to all relevant peers. + """ + + def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do + public = is_public?(activity) + {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) + + recipients = recipients(actor, activity) + + recipients + |> Enum.filter(&User.ap_enabled?/1) + |> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end) + |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) + |> Instances.filter_reachable() + |> Enum.each(fn {inbox, unreachable_since} -> + %User{ap_id: ap_id} = + Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end) + + cc = get_cc_ap_ids(ap_id, recipients) + + json = + data + |> Map.put("cc", cc) + |> Map.put("directMessage", true) + |> Jason.encode!() + + Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{ + inbox: inbox, + json: json, + actor: actor, + id: activity.data["id"], + unreachable_since: unreachable_since + }) + end) + end + + @doc """ + Publishes an activity to all relevant peers. + """ + def publish(%User{} = actor, %Activity{} = activity) do public = is_public?(activity) if public && Config.get([:instance, :allow_relay]) do @@ -115,7 +164,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) json = Jason.encode!(data) - (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers) + recipients(actor, activity) |> Enum.filter(fn user -> User.ap_enabled?(user) end) |> Enum.map(fn %{info: %{source_data: data}} -> (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"] diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 508f3532f..c4cc77a95 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -741,13 +741,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do object = - Object.normalize(object_id).data + object_id + |> Object.normalize() + |> Map.get(:data) |> prepare_object data = data |> Map.put("object", object) |> Map.merge(Utils.make_json_ld_header()) + |> Map.delete("bcc") {:ok, data} end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index b53869c75..ed2c0017f 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -120,6 +120,10 @@ defmodule Pleroma.Web.CommonAPI do when visibility in ~w{public unlisted private direct}, do: visibility + def get_visibility(%{"visibility" => "list:" <> list_id}) do + {:list, String.to_integer(list_id)} + end + def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do case get_replied_to_activity(status_id) do nil -> @@ -150,6 +154,7 @@ defmodule Pleroma.Web.CommonAPI do visibility ), {to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility), + bcc <- bcc_for_list(user, visibility), context <- make_context(in_reply_to), cw <- data["spoiler_text"] || "", full_payload <- String.trim(status <> cw), @@ -172,19 +177,16 @@ defmodule Pleroma.Web.CommonAPI do "emoji", Formatter.get_emoji_map(full_payload) ) do - res = - ActivityPub.create( - %{ - to: to, - actor: user, - context: context, - object: object, - additional: %{"cc" => cc, "directMessage" => visibility == "direct"} - }, - Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false - ) - - res + ActivityPub.create( + %{ + to: to, + actor: user, + context: context, + object: object, + additional: %{"cc" => cc, "bcc" => bcc, "directMessage" => visibility == "direct"} + }, + Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false + ) end end @@ -193,7 +195,7 @@ defmodule Pleroma.Web.CommonAPI do user = with emoji <- emoji_from_profile(user), source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji), - info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data), + info_cng <- User.Info.set_source_data(user.info, source_data), change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), {:ok, user} <- User.update_and_set_cache(change) do user @@ -226,7 +228,7 @@ defmodule Pleroma.Web.CommonAPI do } = activity <- get_by_id_or_ap_id(id_or_ap_id), true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"), %{valid?: true} = info_changeset <- - Pleroma.User.Info.add_pinnned_activity(user.info, activity), + User.Info.add_pinnned_activity(user.info, activity), changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset), {:ok, _user} <- User.update_and_set_cache(changeset) do @@ -243,7 +245,7 @@ defmodule Pleroma.Web.CommonAPI do def unpin(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), %{valid?: true} = info_changeset <- - Pleroma.User.Info.remove_pinnned_activity(user.info, activity), + User.Info.remove_pinnned_activity(user.info, activity), changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset), {:ok, _user} <- User.update_and_set_cache(changeset) do diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 1dfe50b40..f082b77d8 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -102,6 +102,14 @@ defmodule Pleroma.Web.CommonAPI.Utils do end end + def to_for_user_and_mentions(_user, _mentions, _inReplyTo, _), do: {[], []} + + def bcc_for_list(user, {:list, list_id}) do + [Pleroma.List.ap_id(user, list_id)] + end + + def bcc_for_list(_, _), do: [] + def make_content_html( status, attachments, diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 42709ab47..80c3a3190 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -162,11 +162,26 @@ defmodule Pleroma.Web.Salmon do {:ok, salmon} end - def remote_users(%{data: %{"to" => to} = data}) do - to = to ++ (data["cc"] || []) + def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do + cc = Map.get(data, "cc", []) + + bcc = + data + |> Map.get("bcc", []) + |> Enum.reduce([], fn ap_id, bcc -> + case Pleroma.List.get_by_ap_id(ap_id) do + %Pleroma.List{user_id: ^user_id} = list -> + {:ok, following} = Pleroma.List.get_following(list) + bcc ++ Enum.map(following, & &1.ap_id) + + _ -> + bcc + end + end) - to - |> Enum.map(fn id -> User.get_cached_by_ap_id(id) end) + [to, cc, bcc] + |> Enum.concat() + |> Enum.map(&User.get_cached_by_ap_id/1) |> Enum.filter(fn user -> user && !user.local end) end @@ -230,7 +245,7 @@ defmodule Pleroma.Web.Salmon do {:ok, private, _} = keys_from_pem(keys) {:ok, feed} = encode(private, feed) - remote_users = remote_users(activity) + remote_users = remote_users(user, activity) salmon_urls = Enum.map(remote_users, & &1.info.salmon) reachable_urls_metadata = Instances.filter_reachable(salmon_urls) |