summaryrefslogtreecommitdiff
path: root/lib/pleroma/web/activity_pub
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/web/activity_pub')
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex256
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex90
-rw-r--r--lib/pleroma/web/activity_pub/mrf.ex10
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex48
-rw-r--r--lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex17
-rw-r--r--lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex56
-rw-r--r--lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex12
-rw-r--r--lib/pleroma/web/activity_pub/mrf/normalize_markup.ex10
-rw-r--r--lib/pleroma/web/activity_pub/mrf/reject_non_public.ex38
-rw-r--r--lib/pleroma/web/activity_pub/mrf/subchain_policy.ex40
-rw-r--r--lib/pleroma/web/activity_pub/mrf/tag_policy.ex53
-rw-r--r--lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex (renamed from lib/pleroma/web/activity_pub/mrf/user_allowlist.ex)7
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex66
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex250
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex67
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex39
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex24
17 files changed, 780 insertions, 303 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index aa0229db7..31397b09f 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -4,9 +4,11 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity
+ alias Pleroma.Config
alias Pleroma.Conversation
alias Pleroma.Notification
alias Pleroma.Object
+ alias Pleroma.Object.Containment
alias Pleroma.Object.Fetcher
alias Pleroma.Pagination
alias Pleroma.Repo
@@ -25,19 +27,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 +44,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
@@ -73,7 +74,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
- limit = Pleroma.Config.get([:instance, :remote_limit])
+ limit = Config.get([:instance, :remote_limit])
String.length(content) <= limit
end
@@ -108,6 +109,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def decrease_replies_count_if_reply(_object), do: :noop
+ def increase_poll_votes_if_vote(%{
+ "object" => %{"inReplyTo" => reply_ap_id, "name" => name},
+ "type" => "Create"
+ }) do
+ Object.increase_vote_count(reply_ap_id, name)
+ end
+
+ def increase_poll_votes_if_vote(_create_data), do: :noop
+
def insert(map, local \\ true, fake \\ false) when is_map(map) do
with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map, fake),
@@ -116,6 +126,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 <- Containment.contain_child(map),
{:ok, map, object} <- insert_full_object(map) do
{:ok, activity} =
Repo.insert(%Activity{
@@ -179,44 +190,62 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end)
end
+ def stream_out_participations(%Object{data: %{"context" => context}}, user) do
+ with %Conversation{} = conversation <- Conversation.get_for_ap_id(context),
+ conversation = Repo.preload(conversation, :participations),
+ last_activity_id =
+ fetch_latest_activity_id_for_context(conversation.ap_id, %{
+ "user" => user,
+ "blocking_user" => user
+ }) do
+ if last_activity_id do
+ stream_out_participations(conversation.participations)
+ end
+ end
+ end
+
+ def stream_out_participations(_, _), do: :noop
+
def stream_out(activity) do
public = "https://www.w3.org/ns/activitystreams#Public"
if activity.data["type"] in ["Create", "Announce", "Delete"] do
- Pleroma.Web.Streamer.stream("user", activity)
- Pleroma.Web.Streamer.stream("list", activity)
-
- if Enum.member?(activity.data["to"], public) do
- Pleroma.Web.Streamer.stream("public", activity)
+ object = Object.normalize(activity)
+ # Do not stream out poll replies
+ unless object.data["type"] == "Answer" do
+ Pleroma.Web.Streamer.stream("user", activity)
+ Pleroma.Web.Streamer.stream("list", activity)
- if activity.local do
- Pleroma.Web.Streamer.stream("public:local", activity)
- end
+ if Enum.member?(activity.data["to"], public) do
+ Pleroma.Web.Streamer.stream("public", activity)
- if activity.data["type"] in ["Create"] do
- object = Object.normalize(activity)
+ if activity.local do
+ Pleroma.Web.Streamer.stream("public:local", activity)
+ end
- 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["type"] in ["Create"] do
+ 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 object.data["attachment"] != [] do
- Pleroma.Web.Streamer.stream("public:media", activity)
+ if object.data["attachment"] != [] do
+ Pleroma.Web.Streamer.stream("public:media", activity)
- if activity.local do
- Pleroma.Web.Streamer.stream("public:local:media", activity)
+ if activity.local do
+ Pleroma.Web.Streamer.stream("public:local:media", activity)
+ end
end
end
+ else
+ # TODO: Write test, replace with visibility test
+ if !Enum.member?(activity.data["cc"] || [], public) &&
+ !Enum.member?(
+ activity.data["to"],
+ User.get_cached_by_ap_id(activity.data["actor"]).follower_address
+ ),
+ do: Pleroma.Web.Streamer.stream("direct", activity)
end
- else
- # TODO: Write test, replace with visibility test
- if !Enum.member?(activity.data["cc"] || [], public) &&
- !Enum.member?(
- activity.data["to"],
- User.get_cached_by_ap_id(activity.data["actor"]).follower_address
- ),
- do: Pleroma.Web.Streamer.stream("direct", activity)
end
end
end
@@ -235,6 +264,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, activity} <- insert(create_data, local, fake),
{:fake, false, activity} <- {:fake, fake, activity},
_ <- increase_replies_count_if_reply(create_data),
+ _ <- increase_poll_votes_if_vote(create_data),
# Changing note count prior to enqueuing federation task in order to avoid
# race conditions on updating user.info
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
@@ -376,6 +406,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
+ def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do
+ with data <- %{
+ "to" => [follower_address],
+ "type" => "Delete",
+ "actor" => ap_id,
+ "object" => %{"type" => "Person", "id" => ap_id}
+ },
+ {:ok, activity} <- insert(data, true, true),
+ :ok <- maybe_federate(activity) do
+ {:ok, user}
+ end
+ end
+
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
user = User.get_cached_by_ap_id(actor)
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
@@ -388,7 +431,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"to" => to,
"deleted_activity_id" => activity && activity.id
},
- {:ok, activity} <- insert(data, local),
+ {:ok, activity} <- insert(data, local, false),
+ stream_out_participations(object, user),
_ <- decrease_replies_count_if_reply(object),
# Changing note count prior to enqueuing federation task in order to avoid
# race conditions on updating user.info
@@ -399,16 +443,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
- ap_config = Application.get_env(:pleroma, :activitypub)
- unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked)
- outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks)
+ outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
+ unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
- with true <- unfollow_blocked do
+ if unfollow_blocked do
follow_activity = fetch_latest_follow(blocker, blocked)
-
- if follow_activity do
- unfollow(blocker, blocked, nil, local)
- end
+ if follow_activity, do: unfollow(blocker, blocked, nil, local)
end
with true <- outgoing_blocks,
@@ -480,6 +520,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
from(activity in Activity)
+ |> maybe_preload_objects(opts)
|> restrict_blocked(opts)
|> restrict_recipients(recipients, opts["user"])
|> where(
@@ -492,6 +533,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
^context
)
)
+ |> exclude_poll_votes(opts)
|> order_by([activity], desc: activity.id)
end
@@ -499,7 +541,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def fetch_activities_for_context(context, opts \\ %{}) do
context
|> fetch_activities_for_context_query(opts)
- |> Activity.with_preloaded_object()
|> Repo.all()
end
@@ -507,7 +548,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Pleroma.FlakeId.t() | nil
def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
context
- |> fetch_activities_for_context_query(opts)
+ |> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts))
|> limit(1)
|> select([a], a.id)
|> Repo.one()
@@ -548,14 +589,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_visibility(query, %{visibility: visibility})
when visibility in @valid_visibilities do
- query =
- from(
- a in query,
- where:
- fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
- )
-
- query
+ from(
+ a in query,
+ where:
+ fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
+ )
end
defp restrict_visibility(_query, %{visibility: visibility})
@@ -565,17 +603,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_visibility(query, _visibility), do: query
- defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}) do
- query =
- from(
- a in query,
- where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
- )
+ defp restrict_thread_visibility(query, _, %{skip_thread_containment: true} = _),
+ do: query
- query
+ defp restrict_thread_visibility(
+ query,
+ %{"user" => %User{info: %{skip_thread_containment: true}}},
+ _
+ ),
+ do: query
+
+ defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}, _) do
+ from(
+ a in query,
+ where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
+ )
end
- defp restrict_thread_visibility(query, _), do: query
+ defp restrict_thread_visibility(query, _, _), do: query
def fetch_user_activities(user, reading_user, params \\ %{}) do
params =
@@ -653,20 +698,6 @@ 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
@@ -820,6 +851,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_muted_reblogs(query, _), do: query
+ defp exclude_poll_votes(query, %{"include_poll_votes" => "true"}), do: query
+
+ defp exclude_poll_votes(query, _) do
+ if has_named_binding?(query, :object) do
+ from([activity, object: o] in query,
+ where: fragment("not(?->>'type' = ?)", o.data, "Answer")
+ )
+ else
+ query
+ end
+ end
+
defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
defp maybe_preload_objects(query, _) do
@@ -854,9 +897,11 @@ 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)
+ config = %{
+ skip_thread_containment: Config.get([:instance, :skip_thread_containment])
+ }
- base_query
+ Activity
|> maybe_preload_objects(opts)
|> maybe_preload_bookmarks(opts)
|> maybe_set_thread_muted_field(opts)
@@ -875,23 +920,53 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_muted(opts)
|> restrict_media(opts)
|> restrict_visibility(opts)
- |> restrict_thread_visibility(opts)
+ |> restrict_thread_visibility(opts, config)
|> restrict_replies(opts)
|> restrict_reblogs(opts)
|> restrict_pinned(opts)
|> restrict_muted_reblogs(opts)
|> Activity.restrict_deactivated_users()
+ |> exclude_poll_votes(opts)
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, list_memberships, %User{ap_id: user_ap_id})
+ when is_list(list_memberships) and length(list_memberships) > 0 do
+ Enum.map(activities, fn
+ %{data: %{"bcc" => bcc}} = activity when is_list(bcc) and length(bcc) > 0 ->
+ 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
+
+ defp maybe_update_cc(activities, _, _), do: activities
+
+ def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
+ from(activity in query,
+ where:
+ fragment("? && ?", activity.recipients, ^recipients) or
+ (fragment("? && ?", activity.recipients, ^recipients_with_public) and
+ "https://www.w3.org/ns/activitystreams#Public" in activity.recipients)
+ )
end
- def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do
+ def fetch_activities_bounded(recipients, recipients_with_public, opts \\ %{}) do
fetch_activities_query([], opts)
- |> restrict_to_cc(recipients_to, recipients_cc)
+ |> fetch_activities_bounded_query(recipients, recipients_with_public)
|> Pagination.fetch_paginated(opts)
|> Enum.reverse()
end
@@ -938,6 +1013,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
avatar: avatar,
name: data["name"],
follower_address: data["followers"],
+ following_address: data["following"],
bio: data["summary"]
}
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index ad2ca1e54..e2af4ad1a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -27,13 +27,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
plug(:relay_active? when action in [:relay])
def relay_active?(conn, _) do
- if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
+ if Pleroma.Config.get([:instance, :allow_relay]) do
conn
else
conn
- |> put_status(404)
- |> json(%{error: "not found"})
- |> halt
+ |> render_error(:not_found, "not found")
+ |> halt()
end
end
@@ -104,43 +103,57 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
end
- def following(conn, %{"nickname" => nickname, "page" => page}) do
+ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
+ {:show_follows, true} <-
+ {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
{page, _} = Integer.parse(page)
conn
|> put_resp_header("content-type", "application/activity+json")
- |> json(UserView.render("following.json", %{user: user, page: page}))
+ |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
+ else
+ {:show_follows, _} ->
+ conn
+ |> put_resp_header("content-type", "application/activity+json")
+ |> send_resp(403, "")
end
end
- def following(conn, %{"nickname" => nickname}) do
+ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
conn
|> put_resp_header("content-type", "application/activity+json")
- |> json(UserView.render("following.json", %{user: user}))
+ |> json(UserView.render("following.json", %{user: user, for: for_user}))
end
end
- def followers(conn, %{"nickname" => nickname, "page" => page}) do
+ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
+ {:show_followers, true} <-
+ {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
{page, _} = Integer.parse(page)
conn
|> put_resp_header("content-type", "application/activity+json")
- |> json(UserView.render("followers.json", %{user: user, page: page}))
+ |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
+ else
+ {:show_followers, _} ->
+ conn
+ |> put_resp_header("content-type", "application/activity+json")
+ |> send_resp(403, "")
end
end
- def followers(conn, %{"nickname" => nickname}) do
+ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
- {:ok, user} <- User.ensure_keys_present(user) do
+ {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
conn
|> put_resp_header("content-type", "application/activity+json")
- |> json(UserView.render("followers.json", %{user: user}))
+ |> json(UserView.render("followers.json", %{user: user, for: for_user}))
end
end
@@ -190,7 +203,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
Logger.info(inspect(conn.req_headers))
end
- json(conn, "error")
+ json(conn, dgettext("errors", "error"))
end
def relay(conn, _params) do
@@ -218,9 +231,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
else
+ err =
+ dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
+ nickname: nickname,
+ as_nickname: user.nickname
+ )
+
conn
|> put_status(:forbidden)
- |> json("can't read inbox of #{nickname} as #{user.nickname}")
+ |> json(err)
end
end
@@ -246,7 +265,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
{:ok, delete} <- ActivityPub.delete(object) do
{:ok, delete}
else
- _ -> {:error, "Can't delete object"}
+ _ -> {:error, dgettext("errors", "Can't delete object")}
end
end
@@ -255,12 +274,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
{:ok, activity, _object} <- ActivityPub.like(user, object) do
{:ok, activity}
else
- _ -> {:error, "Can't like object"}
+ _ -> {:error, dgettext("errors", "Can't like object")}
end
end
def handle_user_activity(_, _) do
- {:error, "Unhandled activity type"}
+ {:error, dgettext("errors", "Unhandled activity type")}
end
def update_outbox(
@@ -288,22 +307,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> json(message)
end
else
+ err =
+ dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
+ nickname: nickname,
+ as_nickname: user.nickname
+ )
+
conn
|> put_status(:forbidden)
- |> json("can't update outbox of #{nickname} as #{user.nickname}")
+ |> json(err)
end
end
def errors(conn, {:error, :not_found}) do
conn
- |> put_status(404)
- |> json("Not found")
+ |> put_status(:not_found)
+ |> json(dgettext("errors", "Not found"))
end
def errors(conn, _e) do
conn
- |> put_status(500)
- |> json("error")
+ |> put_status(:internal_server_error)
+ |> json(dgettext("errors", "error"))
end
defp set_requester_reachable(%Plug.Conn{} = conn, _) do
@@ -314,4 +339,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn
end
+
+ defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
+ {:ok, new_user} = User.ensure_keys_present(user)
+
+ for_user =
+ if new_user != user and match?(%User{}, for_user) do
+ User.get_cached_by_nickname(for_user.nickname)
+ else
+ for_user
+ end
+
+ {new_user, for_user}
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index 1aaa20050..10ceef715 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -5,8 +5,8 @@
defmodule Pleroma.Web.ActivityPub.MRF do
@callback filter(Map.t()) :: {:ok | :reject, Map.t()}
- def filter(object) do
- get_policies()
+ def filter(policies, %{} = object) do
+ policies
|> Enum.reduce({:ok, object}, fn
policy, {:ok, object} ->
policy.filter(object)
@@ -16,10 +16,10 @@ defmodule Pleroma.Web.ActivityPub.MRF do
end)
end
+ def filter(%{} = object), do: get_policies() |> filter(object)
+
def get_policies do
- Application.get_env(:pleroma, :instance, [])
- |> Keyword.get(:rewrite_policy, [])
- |> get_policies()
+ Pleroma.Config.get([:instance, :rewrite_policy], []) |> get_policies()
end
defp get_policies(policy) when is_atom(policy), do: [policy]
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
new file mode 100644
index 000000000..2da3eac2f
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
@@ -0,0 +1,48 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
+ alias Pleroma.User
+
+ require Logger
+
+ # has the user successfully posted before?
+ defp old_user?(%User{} = u) do
+ u.info.note_count > 0 || u.info.follower_count > 0
+ end
+
+ # does the post contain links?
+ defp contains_links?(%{"content" => content} = _object) do
+ content
+ |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"],a.zrl")
+ |> Floki.attribute("a", "href")
+ |> length() > 0
+ end
+
+ defp contains_links?(_), do: false
+
+ def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
+ with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
+ {:contains_links, true} <- {:contains_links, contains_links?(object)},
+ {:old_user, true} <- {:old_user, old_user?(u)} do
+ {:ok, message}
+ else
+ {:contains_links, false} ->
+ {:ok, message}
+
+ {:old_user, false} ->
+ {:reject, nil}
+
+ {:error, _} ->
+ {:reject, nil}
+
+ e ->
+ Logger.warn("[MRF anti-link-spam] WTF: unhandled error #{inspect(e)}")
+ {:reject, nil}
+ end
+ end
+
+ # in all other cases, pass through
+ def filter(message), do: {:ok, message}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
index 15d8514be..2d03df68a 100644
--- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
+++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
@@ -9,8 +9,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
@behaviour Pleroma.Web.ActivityPub.MRF
@reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless])
+
def filter_by_summary(
- %{"summary" => parent_summary} = _parent,
+ %{data: %{"summary" => parent_summary}} = _in_reply_to,
%{"summary" => child_summary} = child
)
when not is_nil(child_summary) and byte_size(child_summary) > 0 and
@@ -24,17 +25,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
end
end
- def filter_by_summary(_parent, child), do: child
-
- def filter(%{"type" => activity_type} = object) when activity_type == "Create" do
- child = object["object"]
- in_reply_to = Object.normalize(child["inReplyTo"])
+ def filter_by_summary(_in_reply_to, child), do: child
+ def filter(%{"type" => "Create", "object" => child_object} = object) do
child =
- if(in_reply_to,
- do: filter_by_summary(in_reply_to.data, child),
- else: child
- )
+ child_object["inReplyTo"]
+ |> Object.normalize(child_object["inReplyTo"])
+ |> filter_by_summary(child_object)
object = Map.put(object, "object", child)
diff --git a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex
new file mode 100644
index 000000000..01d21a299
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex
@@ -0,0 +1,56 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
+ @moduledoc "Preloads any attachments in the MediaProxy cache by prefetching them"
+ @behaviour Pleroma.Web.ActivityPub.MRF
+
+ alias Pleroma.HTTP
+ alias Pleroma.Web.MediaProxy
+
+ require Logger
+
+ @hackney_options [
+ pool: :media,
+ recv_timeout: 10_000
+ ]
+
+ def perform(:prefetch, url) do
+ Logger.info("Prefetching #{inspect(url)}")
+
+ url
+ |> MediaProxy.url()
+ |> HTTP.get([], adapter: @hackney_options)
+ end
+
+ def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do
+ Enum.each(attachments, fn
+ %{"url" => url} when is_list(url) ->
+ url
+ |> Enum.each(fn
+ %{"href" => href} ->
+ PleromaJobQueue.enqueue(:background, __MODULE__, [:prefetch, href])
+
+ x ->
+ Logger.debug("Unhandled attachment URL object #{inspect(x)}")
+ end)
+
+ x ->
+ Logger.debug("Unhandled attachment #{inspect(x)}")
+ end)
+ end
+
+ @impl true
+ def filter(
+ %{"type" => "Create", "object" => %{"attachment" => attachments} = _object} = message
+ )
+ when is_list(attachments) and length(attachments) > 0 do
+ PleromaJobQueue.enqueue(:background, __MODULE__, [:preload, message])
+
+ {:ok, message}
+ end
+
+ @impl true
+ def filter(message), do: {:ok, message}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
index f30fee0d5..86a48bda5 100644
--- a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
@@ -10,19 +10,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
def filter(
%{
"type" => "Create",
- "object" => %{"content" => content, "attachment" => _attachment} = child_object
+ "object" => %{"content" => content, "attachment" => _} = _child_object
} = object
)
when content in [".", "<p>.</p>"] do
- child_object =
- child_object
- |> Map.put("content", "")
-
- object =
- object
- |> Map.put("object", child_object)
-
- {:ok, object}
+ {:ok, put_in(object, ["object", "content"], "")}
end
@impl true
diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
index 9c87c6963..c269d0f89 100644
--- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
+++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
@@ -8,18 +8,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
@behaviour Pleroma.Web.ActivityPub.MRF
- def filter(%{"type" => activity_type} = object) when activity_type == "Create" do
+ def filter(%{"type" => "Create", "object" => child_object} = object) do
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
- child = object["object"]
-
content =
- child["content"]
+ child_object["content"]
|> HTML.filter_tags(scrub_policy)
- child = Map.put(child, "content", content)
-
- object = Map.put(object, "object", child)
+ object = put_in(object, ["object", "content"], content)
{: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 ea3df1b4d..da13fd7c7 100644
--- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
+++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
@@ -3,46 +3,42 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
- alias Pleroma.User
@moduledoc "Rejects non-public (followers-only, direct) activities"
+
+ alias Pleroma.Config
+ alias Pleroma.User
+
@behaviour Pleroma.Web.ActivityPub.MRF
+ @public "https://www.w3.org/ns/activitystreams#Public"
+
@impl true
def filter(%{"type" => "Create"} = object) 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"
+ @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)
+ policy = Config.get(:mrf_rejectnonpublic)
+
+ cond do
+ visibility in ["public", "unlisted"] ->
+ {:ok, object}
- case visibility do
- "public" ->
+ visibility == "followers" and Keyword.get(policy, :allow_followersonly) ->
{:ok, object}
- "unlisted" ->
+ visibility == "direct" and Keyword.get(policy, :allow_direct) ->
{:ok, object}
- "followers" ->
- with true <- Keyword.get(policy, :allow_followersonly) do
- {:ok, object}
- else
- _e -> {:reject, nil}
- end
-
- "direct" ->
- with true <- Keyword.get(policy, :allow_direct) do
- {:ok, object}
- else
- _e -> {:reject, nil}
- end
+ true ->
+ {:reject, nil}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
new file mode 100644
index 000000000..765704389
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
+ alias Pleroma.Config
+ alias Pleroma.Web.ActivityPub.MRF
+
+ require Logger
+
+ @behaviour MRF
+
+ defp lookup_subchain(actor) do
+ with matches <- Config.get([:mrf_subchain, :match_actor]),
+ {match, subchain} <- Enum.find(matches, fn {k, _v} -> String.match?(actor, k) end) do
+ {:ok, match, subchain}
+ else
+ _e -> {:error, :notfound}
+ end
+ end
+
+ @impl true
+ def filter(%{"actor" => actor} = message) do
+ with {:ok, match, subchain} <- lookup_subchain(actor) do
+ Logger.debug(
+ "[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{
+ inspect(subchain)
+ }"
+ )
+
+ subchain
+ |> MRF.filter(message)
+ else
+ _e -> {:ok, message}
+ end
+ end
+
+ @impl true
+ def filter(message), do: {:ok, message}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
index 6683b8d8e..b42c4ed76 100644
--- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
@@ -19,12 +19,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
- `mrf_tag:disable-any-subscription`: Reject any follow requests
"""
+ @public "https://www.w3.org/ns/activitystreams#Public"
+
defp get_tags(%User{tags: tags}) when is_list(tags), do: tags
defp get_tags(_), do: []
defp process_tag(
"mrf_tag:media-force-nsfw",
- %{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message
+ %{
+ "type" => "Create",
+ "object" => %{"attachment" => child_attachment} = object
+ } = message
)
when length(child_attachment) > 0 do
tags = (object["tag"] || []) ++ ["nsfw"]
@@ -41,7 +46,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
defp process_tag(
"mrf_tag:media-strip",
- %{"type" => "Create", "object" => %{"attachment" => child_attachment} = object} = message
+ %{
+ "type" => "Create",
+ "object" => %{"attachment" => child_attachment} = object
+ } = message
)
when length(child_attachment) > 0 do
object = Map.delete(object, "attachment")
@@ -52,19 +60,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
defp process_tag(
"mrf_tag:force-unlisted",
- %{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message
+ %{
+ "type" => "Create",
+ "to" => to,
+ "cc" => cc,
+ "actor" => actor,
+ "object" => object
+ } = message
) do
user = User.get_cached_by_ap_id(actor)
- if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") do
- to =
- List.delete(to, "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address]
-
- cc =
- List.delete(cc, user.follower_address) ++ ["https://www.w3.org/ns/activitystreams#Public"]
+ if Enum.member?(to, @public) do
+ to = List.delete(to, @public) ++ [user.follower_address]
+ cc = List.delete(cc, user.follower_address) ++ [@public]
object =
- message["object"]
+ object
|> Map.put("to", to)
|> Map.put("cc", cc)
@@ -82,19 +93,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
defp process_tag(
"mrf_tag:sandbox",
- %{"type" => "Create", "to" => to, "cc" => cc, "actor" => actor} = message
+ %{
+ "type" => "Create",
+ "to" => to,
+ "cc" => cc,
+ "actor" => actor,
+ "object" => object
+ } = message
) do
user = User.get_cached_by_ap_id(actor)
- if Enum.member?(to, "https://www.w3.org/ns/activitystreams#Public") or
- Enum.member?(cc, "https://www.w3.org/ns/activitystreams#Public") do
- to =
- List.delete(to, "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address]
-
- cc = List.delete(cc, "https://www.w3.org/ns/activitystreams#Public")
+ if Enum.member?(to, @public) or Enum.member?(cc, @public) do
+ to = List.delete(to, @public) ++ [user.follower_address]
+ cc = List.delete(cc, @public)
object =
- message["object"]
+ object
|> Map.put("to", to)
|> Map.put("cc", cc)
@@ -123,7 +137,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
end
end
- defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}), do: {:reject, nil}
+ defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow"}),
+ do: {:reject, nil}
defp process_tag(_, message), do: {:ok, message}
diff --git a/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex b/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex
index 47663414a..e35d2c422 100644
--- a/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex
+++ b/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex
@@ -21,7 +21,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
@impl true
def filter(%{"actor" => actor} = object) do
actor_info = URI.parse(actor)
- allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], [])
+
+ allow_list =
+ Config.get(
+ [:mrf_user_allowlist, String.to_atom(actor_info.host)],
+ []
+ )
filter_by_list(object, allow_list)
end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 8f1399ce6..18145e45f 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -88,22 +88,72 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
true
else
inbox_info = URI.parse(inbox)
- !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
+ !Enum.member?(Config.get([:instance, :quarantined_instances], []), inbox_info.host)
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)
+
+ # Get all the recipients on the same host and add them to cc. Otherwise it a remote
+ # instance would only accept a first message for the first recipient and ignore the rest.
+ cc = get_cc_ap_ids(ap_id, recipients)
+
+ json =
+ data
+ |> Map.put("cc", cc)
+ |> 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
@@ -114,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 5edd8ccc7..602ae48e1 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.Federator
import Ecto.Query
@@ -22,19 +23,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@doc """
Modifies an incoming AP object (mastodon format) to our internal format.
"""
- def fix_object(object) do
+ def fix_object(object, options \\ []) do
object
|> fix_actor
|> fix_url
|> fix_attachments
|> fix_context
- |> fix_in_reply_to
+ |> fix_in_reply_to(options)
|> fix_emoji
|> fix_tag
|> fix_content_map
|> fix_likes
|> fix_addressing
|> fix_summary
+ |> fix_type(options)
end
def fix_summary(%{"summary" => nil} = object) do
@@ -65,7 +67,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, explicit_mentions) do
+ def fix_explicit_addressing(
+ %{"to" => to, "cc" => cc} = object,
+ explicit_mentions,
+ follower_collection
+ ) do
explicit_to =
to
|> Enum.filter(fn x -> x in explicit_mentions end)
@@ -76,6 +82,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
final_cc =
(cc ++ explicit_cc)
+ |> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end)
|> Enum.uniq()
object
@@ -83,7 +90,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("cc", final_cc)
end
- def fix_explicit_addressing(object, _explicit_mentions), do: object
+ def fix_explicit_addressing(object, _explicit_mentions, _followers_collection), do: object
# if directMessage flag is set to true, leave the addressing alone
def fix_explicit_addressing(%{"directMessage" => true} = object), do: object
@@ -93,10 +100,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
object
|> Utils.determine_explicit_mentions()
- explicit_mentions = explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public"]
+ follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address
- object
- |> fix_explicit_addressing(explicit_mentions)
+ explicit_mentions =
+ explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public", follower_collection]
+
+ fix_explicit_addressing(object, explicit_mentions, follower_collection)
end
# if as:Public is addressed, then make sure the followers collection is also addressed
@@ -133,7 +142,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_addressing_list("cc")
|> fix_addressing_list("bto")
|> fix_addressing_list("bcc")
- |> fix_explicit_addressing
+ |> fix_explicit_addressing()
|> fix_implicit_addressing(followers_collection)
end
@@ -156,7 +165,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
object
end
- def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
+ def fix_in_reply_to(object, options \\ [])
+
+ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
when not is_nil(in_reply_to) do
in_reply_to_id =
cond do
@@ -174,28 +185,34 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
""
end
- 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
- object
- |> Map.put("inReplyTo", replied_object.data["id"])
- |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
- |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
- |> Map.put("context", replied_object.data["context"] || object["conversation"])
- else
- e ->
- Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
+ object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
+
+ if Federator.allowed_incoming_reply_depth?(options[:depth]) do
+ case get_obj_helper(in_reply_to_id, options) do
+ {:ok, replied_object} ->
+ with %Activity{} = _activity <-
+ Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
object
- end
+ |> Map.put("inReplyTo", replied_object.data["id"])
+ |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
+ |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
+ |> Map.put("context", replied_object.data["context"] || object["conversation"])
+ else
+ e ->
+ Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
+ object
+ end
- e ->
- Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
- object
+ e ->
+ Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
+ object
+ end
+ else
+ object
end
end
- def fix_in_reply_to(object), do: object
+ def fix_in_reply_to(object, _options), do: object
def fix_context(object) do
context = object["context"] || object["conversation"] || Utils.generate_context_id()
@@ -328,6 +345,23 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_content_map(object), do: object
+ def fix_type(object, options \\ [])
+
+ def fix_type(%{"inReplyTo" => reply_id} = object, options) when is_binary(reply_id) do
+ reply =
+ if Federator.allowed_incoming_reply_depth?(options[:depth]) do
+ Object.normalize(reply_id, true)
+ end
+
+ if reply && (reply.data["type"] == "Question" and object["name"]) do
+ Map.put(object, "type", "Answer")
+ else
+ object
+ end
+ end
+
+ def fix_type(object, _), do: object
+
defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
with true <- id =~ "follows",
%User{local: true} = follower <- User.get_cached_by_ap_id(follower_id),
@@ -354,9 +388,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
+ def handle_incoming(data, options \\ [])
+
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
# with nil ID.
- def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data) do
+ def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data, _options) do
with context <- data["context"] || Utils.generate_context_id(),
content <- data["content"] || "",
%User{} = actor <- User.get_cached_by_ap_id(actor),
@@ -389,16 +425,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
# disallow objects with bogus IDs
- def handle_incoming(%{"id" => nil}), do: :error
- def handle_incoming(%{"id" => ""}), do: :error
+ def handle_incoming(%{"id" => nil}, _options), do: :error
+ def handle_incoming(%{"id" => ""}, _options), do: :error
# length of https:// = 8, should validate better, but good enough for now.
- def handle_incoming(%{"id" => id}) when not (is_binary(id) and length(id) > 8), do: :error
+ def handle_incoming(%{"id" => id}, _options) when not (is_binary(id) and length(id) > 8),
+ do: :error
# TODO: validate those with a Ecto scheme
# - tags
# - emoji
- def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
- when objtype in ["Article", "Note", "Video", "Page"] do
+ def handle_incoming(
+ %{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
+ options
+ )
+ when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do
actor = Containment.get_actor(data)
data =
@@ -407,7 +447,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
- object = fix_object(data["object"])
+ options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
+ object = fix_object(data["object"], options)
params = %{
to: data["to"],
@@ -432,16 +473,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
+ %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
+ _options
) do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
- {:user_blocked, false} <-
+ {_, false} <-
{:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
- {:user_locked, false} <- {:user_locked, User.locked?(followed)},
- {:follow, {:ok, follower}} <- {:follow, User.follow(follower, followed)} do
+ {_, false} <- {:user_locked, User.locked?(followed)},
+ {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
+ {_, {:ok, _}} <-
+ {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")} do
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed,
@@ -450,7 +494,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
})
else
{:user_blocked, true} ->
- {:ok, _} = Utils.update_follow_state(activity, "reject")
+ {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
ActivityPub.reject(%{
to: [follower.ap_id],
@@ -460,7 +504,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
})
{:follow, {:error, _}} ->
- {:ok, _} = Utils.update_follow_state(activity, "reject")
+ {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
ActivityPub.reject(%{
to: [follower.ap_id],
@@ -481,38 +525,35 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data
+ %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %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"),
+ {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
- {:ok, activity} <-
- ActivityPub.accept(%{
- to: follow_activity.data["to"],
- type: "Accept",
- actor: followed,
- object: follow_activity.data["id"],
- local: false
- }) do
- if not User.following?(follower, followed) do
- {:ok, _follower} = User.follow(follower, followed)
- end
-
- {:ok, activity}
+ {:ok, _follower} = User.follow(follower, followed) do
+ ActivityPub.accept(%{
+ to: follow_activity.data["to"],
+ type: "Accept",
+ actor: followed,
+ object: follow_activity.data["id"],
+ local: false
+ })
else
_e -> :error
end
end
def handle_incoming(
- %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data
+ %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %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"),
+ {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, activity} <-
ActivityPub.reject(%{
@@ -531,7 +572,8 @@ 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,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -544,7 +586,8 @@ 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,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -559,7 +602,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming(
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
- data
+ data,
+ _options
)
when object_type in ["Person", "Application", "Service", "Organization"] do
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
@@ -597,7 +641,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# 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,
+ _options
) do
object_id = Utils.get_ap_id(object_id)
@@ -608,7 +653,30 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, activity} <- ActivityPub.delete(object, false) do
{:ok, activity}
else
- _e -> :error
+ nil ->
+ case User.get_cached_by_ap_id(object_id) do
+ %User{ap_id: ^actor} = user ->
+ {:ok, followers} = User.get_followers(user)
+
+ Enum.each(followers, fn follower ->
+ User.unfollow(follower, user)
+ end)
+
+ {:ok, friends} = User.get_friends(user)
+
+ Enum.each(friends, fn followed ->
+ User.unfollow(user, followed)
+ end)
+
+ User.invalidate_cache(user)
+ Repo.delete(user)
+
+ nil ->
+ :error
+ end
+
+ _e ->
+ :error
end
end
@@ -618,7 +686,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Announce", "object" => object_id},
"actor" => _actor,
"id" => id
- } = data
+ } = data,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -636,7 +705,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Follow", "object" => followed},
"actor" => follower,
"id" => id
- } = _data
+ } = _data,
+ _options
) do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
@@ -654,7 +724,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Block", "object" => blocked},
"actor" => blocker,
"id" => id
- } = _data
+ } = _data,
+ _options
) do
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
@@ -668,7 +739,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data
+ %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
+ _options
) do
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
@@ -688,7 +760,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Like", "object" => object_id},
"actor" => _actor,
"id" => id
- } = data
+ } = data,
+ _options
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@@ -700,10 +773,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(_), do: :error
+ def handle_incoming(_, _), do: :error
- def get_obj_helper(id) do
- if object = Object.normalize(id), do: {:ok, object}, else: nil
+ def get_obj_helper(id, options \\ []) do
+ if object = Object.normalize(id, true, options), do: {:ok, object}, else: nil
end
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
@@ -731,6 +804,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> set_reply_to_uri
|> strip_internal_fields
|> strip_internal_tags
+ |> set_type
end
# @doc
@@ -740,13 +814,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
@@ -895,6 +972,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "sensitive", "nsfw" in tags)
end
+ def set_type(%{"type" => "Answer"} = object) do
+ Map.put(object, "type", "Note")
+ end
+
+ def set_type(object), do: object
+
def add_attributed_to(object) do
attributed_to = object["attributedTo"] || object["actor"]
@@ -1007,6 +1090,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
end
+ if Pleroma.Config.get([:instance, :external_user_synchronization]) do
+ update_following_followers_counters(user)
+ end
+
{:ok, user}
else
%User{} = user -> {:ok, user}
@@ -1039,4 +1126,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
data
|> maybe_fix_user_url
end
+
+ def update_following_followers_counters(user) do
+ info = %{}
+
+ following = fetch_counter(user.following_address)
+ info = if following, do: Map.put(info, :following_count, following), else: info
+
+ followers = fetch_counter(user.follower_address)
+ info = if followers, do: Map.put(info, :follower_count, followers), else: info
+
+ User.set_info_cache(user, info)
+ end
+
+ defp fetch_counter(url) do
+ with {:ok, %{body: body, status: code}} when code in 200..299 <-
+ Pleroma.HTTP.get(
+ url,
+ [{:Accept, "application/activity+json"}]
+ ),
+ {:ok, data} <- Jason.decode(body) do
+ data["totalItems"]
+ end
+ end
end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index ca8a0844b..c146f59d4 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -19,18 +19,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
require Logger
- @supported_object_types ["Article", "Note", "Video", "Page"]
+ @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer"]
@supported_report_states ~w(open closed resolved)
@valid_visibilities ~w(public unlisted private direct)
# 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
- case object do
- %{"id" => id} -> id
- id -> id
- end
- end
+ def get_ap_id(%{"id" => id} = _), do: id
+ def get_ap_id(id), do: id
def normalize_params(params) do
Map.put(params, "actor", get_ap_id(params["actor"]))
@@ -151,16 +147,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def create_context(context) do
context = context || generate_id("contexts")
- changeset = Object.context_mapping(context)
- case Repo.insert(changeset) do
- {:ok, object} ->
- object
+ # Ecto has problems accessing the constraint inside the jsonb,
+ # so we explicitly check for the existed object before insert
+ object = Object.get_cached_by_ap_id(context)
- # This should be solved by an upsert, but it seems ecto
- # has problems accessing the constraint inside the jsonb.
- {:error, _} ->
- Object.get_cached_by_ap_id(context)
+ with true <- is_nil(object),
+ changeset <- Object.context_mapping(context),
+ {:ok, inserted_object} <- Repo.insert(changeset) do
+ inserted_object
+ else
+ _ ->
+ object
end
end
@@ -168,14 +166,17 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Enqueues an activity for federation if it's local
"""
def maybe_federate(%Activity{local: true} = activity) do
- priority =
- case activity.data["type"] do
- "Delete" -> 10
- "Create" -> 1
- _ -> 5
- end
+ if Pleroma.Config.get!([:instance, :federating]) do
+ priority =
+ case activity.data["type"] do
+ "Delete" -> 10
+ "Create" -> 1
+ _ -> 5
+ end
+
+ Pleroma.Web.Federator.publish(activity, priority)
+ end
- Pleroma.Web.Federator.publish(activity, priority)
:ok
end
@@ -376,8 +377,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
@doc """
Updates a follow activity's state (for locked accounts).
"""
- def update_follow_state(
- %Activity{data: %{"actor" => actor, "object" => object, "state" => "pending"}} = activity,
+ def update_follow_state_for_all(
+ %Activity{data: %{"actor" => actor, "object" => object}} = activity,
state
) do
try do
@@ -789,4 +790,22 @@ defmodule Pleroma.Web.ActivityPub.Utils do
[to, cc, recipients]
end
end
+
+ def get_existing_votes(actor, %{data: %{"id" => id}}) do
+ query =
+ from(
+ [activity, object: object] in Activity.with_preloaded_object(Activity),
+ where: fragment("(?)->>'type' = 'Create'", activity.data),
+ where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
+ where:
+ fragment(
+ "(?)->>'inReplyTo' = ?",
+ object.data,
+ ^to_string(id)
+ ),
+ where: fragment("(?)->>'type' = 'Answer'", object.data)
+ )
+
+ Repo.all(query)
+ 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 327e0e05b..d9c1bcb2c 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -98,29 +98,31 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|> Map.merge(Utils.make_json_ld_header())
end
- def render("following.json", %{user: user, page: page}) do
+ def render("following.json", %{user: user, page: page} = opts) do
+ showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
query = User.get_friends_query(user)
query = from(user in query, select: [:ap_id])
following = Repo.all(query)
total =
- if !user.info.hide_follows do
+ if showing do
length(following)
else
0
end
- collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total)
+ collection(following, "#{user.ap_id}/following", page, showing, total)
|> Map.merge(Utils.make_json_ld_header())
end
- def render("following.json", %{user: user}) do
+ def render("following.json", %{user: user} = opts) do
+ showing = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
query = User.get_friends_query(user)
query = from(user in query, select: [:ap_id])
following = Repo.all(query)
total =
- if !user.info.hide_follows do
+ if showing do
length(following)
else
0
@@ -130,34 +132,43 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"id" => "#{user.ap_id}/following",
"type" => "OrderedCollection",
"totalItems" => total,
- "first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
+ "first" =>
+ if showing do
+ collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
+ else
+ "#{user.ap_id}/following?page=1"
+ end
}
|> Map.merge(Utils.make_json_ld_header())
end
- def render("followers.json", %{user: user, page: page}) do
+ def render("followers.json", %{user: user, page: page} = opts) do
+ showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
+
query = User.get_followers_query(user)
query = from(user in query, select: [:ap_id])
followers = Repo.all(query)
total =
- if !user.info.hide_followers do
+ if showing do
length(followers)
else
0
end
- collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total)
+ collection(followers, "#{user.ap_id}/followers", page, showing, total)
|> Map.merge(Utils.make_json_ld_header())
end
- def render("followers.json", %{user: user}) do
+ def render("followers.json", %{user: user} = opts) do
+ showing = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
+
query = User.get_followers_query(user)
query = from(user in query, select: [:ap_id])
followers = Repo.all(query)
total =
- if !user.info.hide_followers do
+ if showing do
length(followers)
else
0
@@ -168,7 +179,11 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"type" => "OrderedCollection",
"totalItems" => total,
"first" =>
- collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total)
+ if showing do
+ collection(followers, "#{user.ap_id}/followers", 1, showing, total)
+ else
+ "#{user.ap_id}/followers?page=1"
+ end
}
|> Map.merge(Utils.make_json_ld_header())
end
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 93b50ee47..2666edc7c 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Activity
alias Pleroma.Object
@@ -30,6 +34,20 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
!is_public?(activity) && !is_private?(activity)
end
+ def is_list?(%{data: %{"listMessage" => _}}), do: true
+ def is_list?(_), do: false
+
+ def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
+
+ def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
+ user.ap_id in activity.data["to"] ||
+ list_ap_id
+ |> Pleroma.List.get_by_ap_id()
+ |> Pleroma.List.member?(user)
+ end
+
+ def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
+
def visible_for_user?(activity, nil) do
is_public?(activity)
end
@@ -66,6 +84,12 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
Enum.any?(to, &String.contains?(&1, "/followers")) ->
"private"
+ object.data["directMessage"] == true ->
+ "direct"
+
+ is_binary(object.data["listMessage"]) ->
+ "list"
+
length(cc) > 0 ->
"private"