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.ex131
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex3
-rw-r--r--lib/pleroma/web/activity_pub/builder.ex40
-rw-r--r--lib/pleroma/web/activity_pub/mrf.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex12
-rw-r--r--lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/keyword_policy.ex7
-rw-r--r--lib/pleroma/web/activity_pub/mrf/mention_policy.ex5
-rw-r--r--lib/pleroma/web/activity_pub/mrf/object_age_policy.ex10
-rw-r--r--lib/pleroma/web/activity_pub/mrf/reject_non_public.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex46
-rw-r--r--lib/pleroma/web/activity_pub/mrf/tag_policy.ex7
-rw-r--r--lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex18
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex42
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/block_validator.ex42
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex6
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/follow_validator.ex44
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/update_validator.ex59
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex20
-rw-r--r--lib/pleroma/web/activity_pub/relay.ex2
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex126
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex164
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex9
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex12
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex6
27 files changed, 557 insertions, 270 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 3e4d0a2be..a4db1d87c 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Constants
alias Pleroma.Conversation
alias Pleroma.Conversation.Participation
+ alias Pleroma.Filter
alias Pleroma.Maps
alias Pleroma.Notification
alias Pleroma.Object
@@ -321,50 +322,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- @spec update(map()) :: {:ok, Activity.t()} | {:error, any()}
- def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
- local = !(params[:local] == false)
- activity_id = params[:activity_id]
-
- data =
- %{
- "to" => to,
- "cc" => cc,
- "type" => "Update",
- "actor" => actor,
- "object" => object
- }
- |> Maps.put_if_present("id", activity_id)
-
- with {:ok, activity} <- insert(data, local),
- _ <- notify_and_stream(activity),
- :ok <- maybe_federate(activity) do
- {:ok, activity}
- end
- end
-
- @spec follow(User.t(), User.t(), String.t() | nil, boolean(), keyword()) ::
- {:ok, Activity.t()} | {:error, any()}
- def follow(follower, followed, activity_id \\ nil, local \\ true, opts \\ []) do
- with {:ok, result} <-
- Repo.transaction(fn -> do_follow(follower, followed, activity_id, local, opts) end) do
- result
- end
- end
-
- defp do_follow(follower, followed, activity_id, local, opts) do
- skip_notify_and_stream = Keyword.get(opts, :skip_notify_and_stream, false)
- data = make_follow_data(follower, followed, activity_id)
-
- with {:ok, activity} <- insert(data, local),
- _ <- skip_notify_and_stream || notify_and_stream(activity),
- :ok <- maybe_federate(activity) do
- {:ok, activity}
- else
- {:error, error} -> Repo.rollback(error)
- end
- end
-
@spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) ::
{:ok, Activity.t()} | nil | {:error, any()}
def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
@@ -388,33 +345,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- @spec block(User.t(), User.t(), String.t() | nil, boolean()) ::
- {:ok, Activity.t()} | {:error, any()}
- def block(blocker, blocked, activity_id \\ nil, local \\ true) do
- with {:ok, result} <-
- Repo.transaction(fn -> do_block(blocker, blocked, activity_id, local) end) do
- result
- end
- end
-
- defp do_block(blocker, blocked, activity_id, local) do
- unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
-
- if unfollow_blocked and fetch_latest_follow(blocker, blocked) do
- unfollow(blocker, blocked, nil, local)
- end
-
- block_data = make_block_data(blocker, blocked, activity_id)
-
- with {:ok, activity} <- insert(block_data, local),
- _ <- notify_and_stream(activity),
- :ok <- maybe_federate(activity) do
- {:ok, activity}
- else
- {:error, error} -> Repo.rollback(error)
- end
- end
-
@spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
def flag(
%{
@@ -495,6 +425,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> maybe_set_thread_muted_field(opts)
|> restrict_blocked(opts)
|> restrict_recipients(recipients, opts[:user])
+ |> restrict_filtered(opts)
|> where(
[activity],
fragment(
@@ -1010,6 +941,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_instance(query, _), do: query
+ defp restrict_filtered(query, %{user: %User{} = user}) do
+ case Filter.compose_regex(user) do
+ nil ->
+ query
+
+ regex ->
+ from([activity, object] in query,
+ where:
+ fragment("not(?->>'content' ~* ?)", object.data, ^regex) or
+ activity.actor == ^user.ap_id
+ )
+ end
+ end
+
+ defp restrict_filtered(query, %{blocking_user: %User{} = user}) do
+ restrict_filtered(query, %{user: user})
+ end
+
+ defp restrict_filtered(query, _), do: query
+
defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
defp exclude_poll_votes(query, _) do
@@ -1140,6 +1091,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_favorited_by(opts)
|> restrict_blocked(restrict_blocked_opts)
|> restrict_muted(restrict_muted_opts)
+ |> restrict_filtered(opts)
|> restrict_media(opts)
|> restrict_visibility(opts)
|> restrict_thread_visibility(opts, config)
@@ -1148,6 +1100,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|> restrict_instance(opts)
|> restrict_announce_object_actor(opts)
+ |> restrict_filtered(opts)
|> Activity.restrict_deactivated_users()
|> exclude_poll_votes(opts)
|> exclude_chat_messages(opts)
@@ -1273,6 +1226,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end)
locked = data["manuallyApprovesFollowers"] || false
+ capabilities = data["capabilities"] || %{}
+ accepts_chat_messages = capabilities["acceptsChatMessages"]
data = Transmogrifier.maybe_fix_user_object(data)
discoverable = data["discoverable"] || false
invisible = data["invisible"] || false
@@ -1311,7 +1266,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
also_known_as: Map.get(data, "alsoKnownAs", []),
public_key: public_key,
inbox: data["inbox"],
- shared_inbox: shared_inbox
+ shared_inbox: shared_inbox,
+ accepts_chat_messages: accepts_chat_messages
}
# nickname can be nil because of virtual actors
@@ -1414,12 +1370,41 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
+ {:error, {:reject, reason} = e} ->
+ Logger.info("Rejected user #{ap_id}: #{inspect(reason)}")
+ {:error, e}
+
{:error, e} ->
Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
end
end
+ def maybe_handle_clashing_nickname(data) do
+ nickname = data[:nickname]
+
+ with %User{} = old_user <- User.get_by_nickname(nickname),
+ {_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
+ Logger.info(
+ "Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{
+ data[:ap_id]
+ }, renaming."
+ )
+
+ old_user
+ |> User.remote_user_changeset(%{nickname: "#{old_user.id}.#{old_user.nickname}"})
+ |> User.update_and_set_cache()
+ else
+ {:ap_id_comparison, true} ->
+ Logger.info(
+ "Found an old user for #{nickname}, but the ap id #{data[:ap_id]} is the same as the new user. Race condition? Not changing anything."
+ )
+
+ _ ->
+ nil
+ end
+ end
+
def make_user_from_ap_id(ap_id) do
user = User.get_cached_by_ap_id(ap_id)
@@ -1432,6 +1417,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> User.remote_user_changeset(data)
|> User.update_and_set_cache()
else
+ maybe_handle_clashing_nickname(data)
+
data
|> User.remote_user_changeset()
|> Repo.insert()
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index f0b5c6e93..220c4fe52 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -514,7 +514,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
{new_user, for_user}
end
- # TODO: Add support for "object" field
@doc """
Endpoint based on <https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload>
@@ -525,6 +524,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
Response:
- HTTP Code: 201 Created
- HTTP Body: ActivityPub object to be inserted into another's `attachment` field
+
+ Note: Will not point to a URL with a `Location` header because no standalone Activity has been created.
"""
def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <-
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index 1aac62c69..d5f3610ed 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -14,6 +14,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do
require Pleroma.Constants
+ @spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
+ def follow(follower, followed) do
+ data = %{
+ "id" => Utils.generate_activity_id(),
+ "actor" => follower.ap_id,
+ "type" => "Follow",
+ "object" => followed.ap_id,
+ "to" => [followed.ap_id]
+ }
+
+ {:ok, data, []}
+ end
+
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
def emoji_react(actor, object, emoji) do
with {:ok, data, meta} <- object_action(actor, object) do
@@ -123,6 +136,33 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
end
+ # Retricted to user updates for now, always public
+ @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
+ def update(actor, object) do
+ to = [Pleroma.Constants.as_public(), actor.follower_address]
+
+ {:ok,
+ %{
+ "id" => Utils.generate_activity_id(),
+ "type" => "Update",
+ "actor" => actor.ap_id,
+ "object" => object,
+ "to" => to
+ }, []}
+ end
+
+ @spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
+ def block(blocker, blocked) do
+ {:ok,
+ %{
+ "id" => Utils.generate_activity_id(),
+ "type" => "Block",
+ "actor" => blocker.ap_id,
+ "object" => blocked.ap_id,
+ "to" => [blocked.ap_id]
+ }, []}
+ end
+
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
def announce(actor, object, options \\ []) do
public? = Keyword.get(options, :public, false)
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index 5a4a76085..206d6af52 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
def filter(%{} = object), do: get_policies() |> filter(object)
def get_policies do
- Pleroma.Config.get([:instance, :rewrite_policy], []) |> get_policies()
+ Pleroma.Config.get([:mrf, :policies], []) |> get_policies()
end
defp get_policies(policy) when is_atom(policy), do: [policy]
@@ -51,7 +51,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
get_policies()
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
- exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
+ exclusions = Pleroma.Config.get([:mrf, :transparency_exclusions])
base =
%{
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
index 0270b96ae..b96388489 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
@@ -60,7 +60,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
if score < 0.8 do
{:ok, message}
else
- {:reject, nil}
+ {:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"}
end
end
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
index 9e7800997..b22464111 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
@@ -27,23 +27,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
@impl true
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
- with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
+ with {:ok, %User{local: false} = 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
+ {:ok, %User{local: true}} ->
+ {:ok, message}
+
{:contains_links, false} ->
{:ok, message}
{:old_user, false} ->
- {:reject, nil}
+ {:reject, "[AntiLinkSpamPolicy] User has no posts nor followers"}
{:error, _} ->
- {:reject, nil}
+ {:reject, "[AntiLinkSpamPolicy] Failed to get or fetch user by ap_id"}
e ->
- Logger.warn("[MRF anti-link-spam] WTF: unhandled error #{inspect(e)}")
- {:reject, nil}
+ {:reject, "[AntiLinkSpamPolicy] Unhandled error #{inspect(e)}"}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
index f6b2c4415..9ba07b4e3 100644
--- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
@@ -43,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
defp reject_message(message, threshold) when threshold > 0 do
with {_, recipients} <- get_recipient_count(message) do
if recipients > threshold do
- {:reject, nil}
+ {:reject, "[HellthreadPolicy] #{recipients} recipients is over the limit of #{threshold}"}
else
{:ok, message}
end
@@ -87,7 +87,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
{:ok, message} <- delist_message(message, delist_threshold) do
{:ok, message}
else
- _e -> {:reject, nil}
+ e -> e
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
index 88b0d2b39..15e09dcf0 100644
--- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
string_matches?(content, pattern) or string_matches?(summary, pattern)
end) do
- {:reject, nil}
+ {:reject, "[KeywordPolicy] Matches with rejected keyword"}
else
{:ok, message}
end
@@ -89,8 +89,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
{:ok, message} <- check_replace(message) do
{:ok, message}
else
- _e ->
- {:reject, nil}
+ {:reject, nil} -> {:reject, "[KeywordPolicy] "}
+ {:reject, _} = e -> e
+ _e -> {:reject, "[KeywordPolicy] "}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
index 06f003921..7910ca131 100644
--- a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
@@ -12,8 +12,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
reject_actors = Pleroma.Config.get([:mrf_mention, :actors], [])
recipients = (message["to"] || []) ++ (message["cc"] || [])
- if Enum.any?(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
- {:reject, nil}
+ if rejected_mention =
+ Enum.find(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
+ {:reject, "[MentionPolicy] Rejected for mention of #{rejected_mention}"}
else
{:ok, message}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
index b0ccb63c8..5f111c72f 100644
--- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
defp check_reject(message, actions) do
if :reject in actions do
- {:reject, nil}
+ {:reject, "[ObjectAgePolicy]"}
else
{:ok, message}
end
@@ -47,9 +47,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
{:ok, message}
else
- # Unhandleable error: somebody is messing around, just drop the message.
_e ->
- {:reject, nil}
+ {:reject, "[ObjectAgePolicy] Unhandled error"}
end
else
{:ok, message}
@@ -69,9 +68,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
{:ok, message}
else
- # Unhandleable error: somebody is messing around, just drop the message.
_e ->
- {:reject, nil}
+ {:reject, "[ObjectAgePolicy] Unhandled error"}
end
else
{:ok, message}
@@ -98,7 +96,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
@impl true
def describe do
mrf_object_age =
- Pleroma.Config.get(:mrf_object_age)
+ Config.get(:mrf_object_age)
|> Enum.into(%{})
{:ok, %{mrf_object_age: mrf_object_age}}
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 3092f3272..0b9ed2224 100644
--- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
+++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
{:ok, object}
true ->
- {:reject, nil}
+ {:reject, "[RejectNonPublic] visibility: #{visibility}"}
end
end
@@ -47,5 +47,5 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
@impl true
def describe,
- do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
+ do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index b7dcb1b86..b77b8c7b4 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -3,33 +3,35 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.MRF
@moduledoc "Filter activities depending on their origin instance"
@behaviour Pleroma.Web.ActivityPub.MRF
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.MRF
+
require Pleroma.Constants
defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts =
- Pleroma.Config.get([:mrf_simple, :accept])
+ Config.get([:mrf_simple, :accept])
|> MRF.subdomains_regex()
cond do
accepts == [] -> {:ok, object}
- actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
+ actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
- true -> {:reject, nil}
+ true -> {:reject, "[SimplePolicy] host not in accept list"}
end
end
defp check_reject(%{host: actor_host} = _actor_info, object) do
rejects =
- Pleroma.Config.get([:mrf_simple, :reject])
+ Config.get([:mrf_simple, :reject])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
- {:reject, nil}
+ {:reject, "[SimplePolicy] host in reject list"}
else
{:ok, object}
end
@@ -41,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
)
when length(child_attachment) > 0 do
media_removal =
- Pleroma.Config.get([:mrf_simple, :media_removal])
+ Config.get([:mrf_simple, :media_removal])
|> MRF.subdomains_regex()
object =
@@ -65,7 +67,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
} = object
) do
media_nsfw =
- Pleroma.Config.get([:mrf_simple, :media_nsfw])
+ Config.get([:mrf_simple, :media_nsfw])
|> MRF.subdomains_regex()
object =
@@ -85,7 +87,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
timeline_removal =
- Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
+ Config.get([:mrf_simple, :federated_timeline_removal])
|> MRF.subdomains_regex()
object =
@@ -108,11 +110,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
report_removal =
- Pleroma.Config.get([:mrf_simple, :report_removal])
+ Config.get([:mrf_simple, :report_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do
- {:reject, nil}
+ {:reject, "[SimplePolicy] host in report_removal list"}
else
{:ok, object}
end
@@ -122,7 +124,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
avatar_removal =
- Pleroma.Config.get([:mrf_simple, :avatar_removal])
+ Config.get([:mrf_simple, :avatar_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(avatar_removal, actor_host) do
@@ -136,7 +138,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
banner_removal =
- Pleroma.Config.get([:mrf_simple, :banner_removal])
+ Config.get([:mrf_simple, :banner_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do
@@ -153,11 +155,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
%{host: actor_host} = URI.parse(actor)
reject_deletes =
- Pleroma.Config.get([:mrf_simple, :reject_deletes])
+ Config.get([:mrf_simple, :reject_deletes])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do
- {:reject, nil}
+ {:reject, "[SimplePolicy] host in reject_deletes list"}
else
{:ok, object}
end
@@ -175,7 +177,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, object} <- check_report_removal(actor_info, object) do
{:ok, object}
else
- _e -> {:reject, nil}
+ {:reject, nil} -> {:reject, "[SimplePolicy]"}
+ {:reject, _} = e -> e
+ _ -> {:reject, "[SimplePolicy]"}
end
end
@@ -189,7 +193,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, object} <- check_banner_removal(actor_info, object) do
{:ok, object}
else
- _e -> {:reject, nil}
+ {:reject, nil} -> {:reject, "[SimplePolicy]"}
+ {:reject, _} = e -> e
+ _ -> {:reject, "[SimplePolicy]"}
end
end
@@ -197,10 +203,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
@impl true
def describe do
- exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
+ exclusions = Config.get([:mrf, :transparency_exclusions])
mrf_simple =
- Pleroma.Config.get(:mrf_simple)
+ Config.get(:mrf_simple)
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|> Enum.into(%{})
diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
index c310462cb..febabda08 100644
--- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
@@ -134,12 +134,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
if user.local == true do
{:ok, message}
else
- {:reject, nil}
+ {:reject,
+ "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"}
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", "actor" => actor}),
+ do: {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-any-subscription"}
defp process_tag(_, message), do: {:ok, message}
diff --git a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
index 651aed70f..1a28f2ba2 100644
--- a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
if actor in allow_list do
{:ok, object}
else
- {:reject, nil}
+ {:reject, "[UserAllowListPolicy] #{actor} not in the list"}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
index 6167a74e2..a6c545570 100644
--- a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
@@ -11,22 +11,26 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
with {:ok, _} <- filter(child_message) do
{:ok, message}
else
- {:reject, nil} ->
- {:reject, nil}
+ {:reject, _} = e -> e
end
end
def filter(%{"type" => message_type} = message) do
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
- true <-
- Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type),
- false <-
- length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
+ {_, true} <-
+ {:accepted,
+ Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type)},
+ {_, false} <-
+ {:rejected,
+ length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type)},
{:ok, _} <- filter(message["object"]) do
{:ok, message}
else
- _ -> {:reject, nil}
+ {:reject, _} = e -> e
+ {:accepted, _} -> {:reject, "[VocabularyPolicy] #{message_type} not in accept list"}
+ {:rejected, _} -> {:reject, "[VocabularyPolicy] #{message_type} in reject list"}
+ _ -> {:reject, "[VocabularyPolicy]"}
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 6a83a2c33..df926829c 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -13,16 +13,58 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
def validate(object, meta)
+ def validate(%{"type" => "Follow"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> FollowValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ def validate(%{"type" => "Block"} = block_activity, meta) do
+ with {:ok, block_activity} <-
+ block_activity
+ |> BlockValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ block_activity = stringify_keys(block_activity)
+ outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks])
+
+ meta =
+ if !outgoing_blocks do
+ Keyword.put(meta, :do_not_federate, true)
+ else
+ meta
+ end
+
+ {:ok, block_activity, meta}
+ end
+ end
+
+ def validate(%{"type" => "Update"} = update_activity, meta) do
+ with {:ok, update_activity} <-
+ update_activity
+ |> UpdateValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ update_activity = stringify_keys(update_activity)
+ {:ok, update_activity, meta}
+ end
+ end
+
def validate(%{"type" => "Undo"} = object, meta) do
with {:ok, object} <-
object
diff --git a/lib/pleroma/web/activity_pub/object_validators/block_validator.ex b/lib/pleroma/web/activity_pub/object_validators/block_validator.ex
new file mode 100644
index 000000000..1dde77198
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/block_validator.ex
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+ import Ecto.Changeset
+ import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:type, :string)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:object, ObjectValidators.ObjectID)
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> cast(data, __schema__(:fields))
+ end
+
+ def validate_data(cng) do
+ cng
+ |> validate_required([:id, :type, :actor, :to, :cc, :object])
+ |> validate_inclusion(:type, ["Block"])
+ |> validate_actor_presence()
+ |> validate_actor_presence(field_name: :object)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data
+ |> validate_data
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
index c481d79e0..91b475393 100644
--- a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
@@ -93,12 +93,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
- If both users are in our system
- If at least one of the users in this ChatMessage is a local user
- If the recipient is not blocking the actor
+ - If the recipient is explicitly not accepting chat messages
"""
def validate_local_concern(cng) do
with actor_ap <- get_field(cng, :actor),
{_, %User{} = actor} <- {:find_actor, User.get_cached_by_ap_id(actor_ap)},
{_, %User{} = recipient} <-
{:find_recipient, User.get_cached_by_ap_id(get_field(cng, :to) |> hd())},
+ {_, false} <- {:not_accepting_chats?, recipient.accepts_chat_messages == false},
{_, false} <- {:blocking_actor?, User.blocks?(recipient, actor)},
{_, true} <- {:local?, Enum.any?([actor, recipient], & &1.local)} do
cng
@@ -107,6 +109,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
cng
|> add_error(:actor, "actor is blocked by recipient")
+ {:not_accepting_chats?, true} ->
+ cng
+ |> add_error(:to, "recipient does not accept chat messages")
+
{:local?, false} ->
cng
|> add_error(:actor, "actor and recipient are both remote")
diff --git a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
new file mode 100644
index 000000000..ca2724616
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
@@ -0,0 +1,44 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+ import Ecto.Changeset
+ import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:type, :string)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:object, ObjectValidators.ObjectID)
+ field(:state, :string, default: "pending")
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> cast(data, __schema__(:fields))
+ end
+
+ def validate_data(cng) do
+ cng
+ |> validate_required([:id, :type, :actor, :to, :cc, :object])
+ |> validate_inclusion(:type, ["Follow"])
+ |> validate_inclusion(:state, ~w{pending reject accept})
+ |> validate_actor_presence()
+ |> validate_actor_presence(field_name: :object)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data
+ |> validate_data
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/update_validator.ex b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
new file mode 100644
index 000000000..b4ba5ede0
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/update_validator.ex
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+ import Ecto.Changeset
+ import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:type, :string)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ # In this case, we save the full object in this activity instead of just a
+ # reference, so we can always see what was actually changed by this.
+ field(:object, :map)
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> cast(data, __schema__(:fields))
+ end
+
+ def validate_data(cng) do
+ cng
+ |> validate_required([:id, :type, :actor, :to, :cc, :object])
+ |> validate_inclusion(:type, ["Update"])
+ |> validate_actor_presence()
+ |> validate_updating_rights()
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data
+ |> validate_data
+ end
+
+ # For now we only support updating users, and here the rule is easy:
+ # object id == actor id
+ def validate_updating_rights(cng) do
+ with actor = get_field(cng, :actor),
+ object = get_field(cng, :object),
+ {:ok, object_id} <- ObjectValidators.ObjectID.cast(object),
+ true <- actor == object_id do
+ cng
+ else
+ _e ->
+ cng
+ |> add_error(:object, "Can't be updated by this actor")
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index b70cbd043..d88f7f3ee 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -49,7 +49,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
"""
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.debug("Federating #{id} to #{inbox}")
- %{host: host, path: path} = URI.parse(inbox)
+
+ uri = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
@@ -57,8 +58,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
signature =
Pleroma.Signature.sign(actor, %{
- "(request-target)": "post #{path}",
- host: host,
+ "(request-target)": "post #{uri.path}",
+ host: signature_host(uri),
"content-length": byte_size(json),
digest: digest,
date: date
@@ -76,8 +77,9 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{"digest", digest}
]
) do
- if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
- do: Instances.set_reachable(inbox)
+ if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
+ Instances.set_reachable(inbox)
+ end
result
else
@@ -96,6 +98,14 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|> publish_one()
end
+ defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
+ if port == URI.default_port(scheme) do
+ host
+ else
+ "#{host}:#{port}"
+ end
+ end
+
defp should_federate?(inbox, public) do
if public do
true
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index 484178edd..b09764d2b 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do
def follow(target_instance) do
with %User{} = local_user <- get_actor(),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
- {:ok, activity} <- ActivityPub.follow(local_user, target_user) do
+ {:ok, _, _, activity} <- CommonAPI.follow(local_user, target_user) do
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
{:ok, activity}
else
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 1a1cc675c..1d2c296a5 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -6,8 +6,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
collection, and so on.
"""
alias Pleroma.Activity
+ alias Pleroma.Activity.Ir.Topics
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
+ alias Pleroma.FollowingRelationship
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
@@ -20,6 +22,104 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
def handle(object, meta \\ [])
+ # Tasks this handle
+ # - Follows if possible
+ # - Sends a notification
+ # - Generates accept or reject if appropriate
+ def handle(
+ %{
+ data: %{
+ "id" => follow_id,
+ "type" => "Follow",
+ "object" => followed_user,
+ "actor" => following_user
+ }
+ } = object,
+ meta
+ ) do
+ with %User{} = follower <- User.get_cached_by_ap_id(following_user),
+ %User{} = followed <- User.get_cached_by_ap_id(followed_user),
+ {_, {:ok, _}, _, _} <-
+ {:following, User.follow(follower, followed, :follow_pending), follower, followed} do
+ if followed.local && !followed.locked do
+ Utils.update_follow_state_for_all(object, "accept")
+ FollowingRelationship.update(follower, followed, :follow_accept)
+ User.update_follower_count(followed)
+ User.update_following_count(follower)
+
+ %{
+ to: [following_user],
+ actor: followed,
+ object: follow_id,
+ local: true
+ }
+ |> ActivityPub.accept()
+ end
+ else
+ {:following, {:error, _}, follower, followed} ->
+ Utils.update_follow_state_for_all(object, "reject")
+ FollowingRelationship.update(follower, followed, :follow_reject)
+
+ if followed.local do
+ %{
+ to: [follower.ap_id],
+ actor: followed,
+ object: follow_id,
+ local: true
+ }
+ |> ActivityPub.reject()
+ end
+
+ _ ->
+ nil
+ end
+
+ {:ok, notifications} = Notification.create_notifications(object, do_send: false)
+
+ meta =
+ meta
+ |> add_notifications(notifications)
+
+ updated_object = Activity.get_by_ap_id(follow_id)
+
+ {:ok, updated_object, meta}
+ end
+
+ # Tasks this handles:
+ # - Unfollow and block
+ def handle(
+ %{data: %{"type" => "Block", "object" => blocked_user, "actor" => blocking_user}} =
+ object,
+ meta
+ ) do
+ with %User{} = blocker <- User.get_cached_by_ap_id(blocking_user),
+ %User{} = blocked <- User.get_cached_by_ap_id(blocked_user) do
+ User.block(blocker, blocked)
+ end
+
+ {:ok, object, meta}
+ end
+
+ # Tasks this handles:
+ # - Update the user
+ #
+ # For a local user, we also get a changeset with the full information, so we
+ # can update non-federating, non-activitypub settings as well.
+ def handle(%{data: %{"type" => "Update", "object" => updated_object}} = object, meta) do
+ if changeset = Keyword.get(meta, :user_update_changeset) do
+ changeset
+ |> User.update_and_set_cache()
+ else
+ {:ok, new_user_data} = ActivityPub.user_data_from_user_object(updated_object)
+
+ User.get_by_ap_id(updated_object["id"])
+ |> User.remote_user_changeset(new_user_data)
+ |> User.update_and_set_cache()
+ end
+
+ {:ok, object, meta}
+ end
+
# Tasks this handles:
# - Add like to object
# - Set up notification
@@ -62,7 +162,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
if !User.is_internal_user?(user) do
Notification.create_notifications(object)
- ActivityPub.stream_out(object)
+
+ object
+ |> Topics.get_activity_topics()
+ |> Streamer.stream(object)
end
{:ok, object, meta}
@@ -170,14 +273,20 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:ok, object}
end
- def handle_undoing(%{data: %{"type" => "Like"}} = object) do
- with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
- {:ok, _} <- Utils.remove_like_from_object(object, liked_object),
- {:ok, _} <- Repo.delete(object) do
- :ok
+ defp undo_like(nil, object), do: delete_object(object)
+
+ defp undo_like(%Object{} = liked_object, object) do
+ with {:ok, _} <- Utils.remove_like_from_object(object, liked_object) do
+ delete_object(object)
end
end
+ def handle_undoing(%{data: %{"type" => "Like"}} = object) do
+ object.data["object"]
+ |> Object.get_by_ap_id()
+ |> undo_like(object)
+ end
+
def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do
with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]),
{:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object),
@@ -207,6 +316,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
def handle_undoing(object), do: {:error, ["don't know how to handle", object]}
+ @spec delete_object(Object.t()) :: :ok | {:error, Ecto.Changeset.t()}
+ defp delete_object(object) do
+ with {:ok, _} <- Repo.delete(object), do: :ok
+ end
+
defp send_notifications(meta) do
Keyword.get(meta, :notifications, [])
|> Enum.each(fn notification ->
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 1c60ef8f5..35aa05eb5 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -62,15 +62,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_summary(object), do: Map.put(object, "summary", "")
def fix_addressing_list(map, field) do
+ addrs = map[field]
+
cond do
- is_binary(map[field]) ->
- Map.put(map, field, [map[field]])
+ is_list(addrs) ->
+ Map.put(map, field, Enum.filter(addrs, &is_binary/1))
- is_nil(map[field]) ->
- Map.put(map, field, [])
+ is_binary(addrs) ->
+ Map.put(map, field, [addrs])
true ->
- map
+ Map.put(map, field, [])
end
end
@@ -176,7 +178,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.drop(["conversation"])
else
e ->
- Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
+ Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
object
end
else
@@ -233,18 +235,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
is_map(url) && is_binary(url["href"]) -> url["href"]
is_binary(data["url"]) -> data["url"]
is_binary(data["href"]) -> data["href"]
+ true -> nil
end
- attachment_url =
- %{"href" => href}
- |> Maps.put_if_present("mediaType", media_type)
- |> Maps.put_if_present("type", Map.get(url || %{}, "type"))
+ if href do
+ attachment_url =
+ %{"href" => href}
+ |> Maps.put_if_present("mediaType", media_type)
+ |> Maps.put_if_present("type", Map.get(url || %{}, "type"))
- %{"url" => [attachment_url]}
- |> Maps.put_if_present("mediaType", media_type)
- |> Maps.put_if_present("type", data["type"])
- |> Maps.put_if_present("name", data["name"])
+ %{"url" => [attachment_url]}
+ |> Maps.put_if_present("mediaType", media_type)
+ |> Maps.put_if_present("type", data["type"])
+ |> Maps.put_if_present("name", data["name"])
+ else
+ nil
+ end
end)
+ |> Enum.filter(& &1)
Map.put(object, "attachment", attachments)
end
@@ -263,12 +271,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_url(%{"type" => object_type, "url" => url} = object)
when object_type in ["Video", "Audio"] and is_list(url) do
- first_element = Enum.at(url, 0)
+ attachment =
+ Enum.find(url, fn x ->
+ media_type = x["mediaType"] || x["mimeType"] || ""
- link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
+ is_map(x) and String.starts_with?(media_type, ["audio/", "video/"])
+ end)
+
+ link_element =
+ Enum.find(url, fn x -> is_map(x) and (x["mediaType"] || x["mimeType"]) == "text/html" end)
object
- |> Map.put("attachment", [first_element])
+ |> Map.put("attachment", [attachment])
|> Map.put("url", link_element["href"])
end
@@ -446,12 +460,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
actor = Containment.get_actor(data)
- data =
- Map.put(data, "actor", actor)
- |> fix_addressing
-
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
- {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
+ {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor),
+ data <- Map.put(data, "actor", actor) |> fix_addressing() do
object = fix_object(object, options)
params = %{
@@ -521,66 +532,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
- _options
- ) do
- with %User{local: true} = followed <-
- User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})),
- {:ok, %User{} = follower} <-
- User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})),
- {:ok, activity} <-
- ActivityPub.follow(follower, followed, id, false, skip_notify_and_stream: true) do
- with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
- {_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
- {_, 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")},
- {:ok, _relationship} <-
- FollowingRelationship.update(follower, followed, :follow_accept) do
- ActivityPub.accept(%{
- to: [follower.ap_id],
- actor: followed,
- object: data,
- local: true
- })
- else
- {:user_blocked, true} ->
- {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
- {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
-
- ActivityPub.reject(%{
- to: [follower.ap_id],
- actor: followed,
- object: data,
- local: true
- })
-
- {:follow, {:error, _}} ->
- {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
- {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
-
- ActivityPub.reject(%{
- to: [follower.ap_id],
- actor: followed,
- object: data,
- local: true
- })
-
- {:user_locked, true} ->
- {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
- :noop
- end
-
- ActivityPub.notify_and_stream(activity)
- {:ok, activity}
- else
- _e ->
- :error
- end
- end
-
- def handle_incoming(
%{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data,
_options
) do
@@ -673,7 +624,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(%{"type" => type} = data, _options)
- when type in ["Like", "EmojiReact", "Announce"] do
+ when type in ~w{Like EmojiReact Announce} do
with :ok <- ObjectValidator.fetch_actor_and_object(data),
{:ok, activity, _meta} <-
Pipeline.common_pipeline(data, local: false) do
@@ -684,35 +635,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
- data,
+ %{"type" => type} = 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
- {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
-
- actor
- |> User.remote_user_changeset(new_user_data)
- |> User.update_and_set_cache()
-
- ActivityPub.update(%{
- local: false,
- to: data["to"] || [],
- cc: data["cc"] || [],
- object: object,
- actor: actor_id,
- activity_id: data["id"]
- })
- else
- e ->
- Logger.error(e)
- :error
+ when type in ~w{Update Block Follow} do
+ with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
+ {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
+ {:ok, activity}
end
end
@@ -789,21 +718,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
- _options
- ) do
- with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
- {:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
- {:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
- User.unfollow(blocker, blocked)
- User.block(blocker, blocked)
- {:ok, activity}
- else
- _e -> :error
- end
- end
-
- def handle_incoming(
%{
"type" => "Move",
"actor" => origin_actor,
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index dfae602df..713b0ca1f 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -719,15 +719,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do
case Activity.get_by_ap_id_with_object(id) do
%Activity{} = activity ->
+ activity_actor = User.get_by_ap_id(activity.object.data["actor"])
+
%{
"type" => "Note",
"id" => activity.data["id"],
"content" => activity.object.data["content"],
"published" => activity.object.data["published"],
"actor" =>
- AccountView.render("show.json", %{
- user: User.get_by_ap_id(activity.object.data["actor"])
- })
+ AccountView.render(
+ "show.json",
+ %{user: activity_actor, skip_visibility_check: true}
+ )
}
_ ->
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 4a02b09a1..3a4564912 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -81,6 +81,15 @@ defmodule Pleroma.Web.ActivityPub.UserView do
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
+ capabilities =
+ if is_boolean(user.accepts_chat_messages) do
+ %{
+ "acceptsChatMessages" => user.accepts_chat_messages
+ }
+ else
+ %{}
+ end
+
%{
"id" => user.ap_id,
"type" => user.actor_type,
@@ -101,7 +110,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"endpoints" => endpoints,
"attachment" => fields,
"tag" => emoji_tags,
- "discoverable" => user.discoverable
+ "discoverable" => user.discoverable,
+ "capabilities" => capabilities
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 453a6842e..343f41caa 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -47,6 +47,10 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
@spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean()
def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
+ def visible_for_user?(nil, _), do: false
+
+ def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
+
def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
user.ap_id in activity.data["to"] ||
list_ap_id
@@ -54,8 +58,6 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|> Pleroma.List.member?(user)
end
- def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
-
def visible_for_user?(%{local: local} = activity, nil) do
cfg_key =
if local,