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.ex246
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex40
-rw-r--r--lib/pleroma/web/activity_pub/builder.ex90
-rw-r--r--lib/pleroma/web/activity_pub/mrf.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex8
-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/ensure_re_prepended.ex3
-rw-r--r--lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex56
-rw-r--r--lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex11
-rw-r--r--lib/pleroma/web/activity_pub/mrf/keyword_policy.ex7
-rw-r--r--lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex12
-rw-r--r--lib/pleroma/web/activity_pub/mrf/mention_policy.ex5
-rw-r--r--lib/pleroma/web/activity_pub/mrf/object_age_policy.ex23
-rw-r--r--lib/pleroma/web/activity_pub/mrf/reject_non_public.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex77
-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.ex152
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex56
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/announce_validator.ex14
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/answer_validator.ex62
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex42
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/audio_validator.ex107
-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.ex20
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_fixes.ex22
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_validations.ex73
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex10
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex144
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex15
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/delete_validator.ex42
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex12
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/event_validator.ex97
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/follow_validator.ex44
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/like_validator.ex14
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/note_validator.ex45
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex37
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_validator.ex112
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/types/date_time.ex34
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/types/object_id.ex23
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/types/recipients.ex36
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/types/safe_text.ex25
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/types/uri.ex20
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/undo_validator.ex12
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/update_validator.ex59
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex10
-rw-r--r--lib/pleroma/web/activity_pub/pipeline.ex7
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex20
-rw-r--r--lib/pleroma/web/activity_pub/relay.ex58
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex210
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex329
-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.ex13
56 files changed, 1871 insertions, 795 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index c9dc6135c..66a9f78a3 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -5,11 +5,11 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
- alias Pleroma.ActivityExpiration
alias Pleroma.Config
alias Pleroma.Constants
alias Pleroma.Conversation
alias Pleroma.Conversation.Participation
+ alias Pleroma.Filter
alias Pleroma.Maps
alias Pleroma.Notification
alias Pleroma.Object
@@ -65,7 +65,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp check_remote_limit(_), do: true
- defp increase_note_count_if_public(actor, object) do
+ def increase_note_count_if_public(actor, object) do
if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
end
@@ -84,17 +84,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp increase_replies_count_if_reply(_create_data), do: :noop
- defp increase_poll_votes_if_vote(%{
- "object" => %{"inReplyTo" => reply_ap_id, "name" => name},
- "type" => "Create",
- "actor" => actor
- }) do
- Object.increase_vote_count(reply_ap_id, name, actor)
- end
-
- defp increase_poll_votes_if_vote(_create_data), do: :noop
-
- @object_types ["ChatMessage"]
+ @object_types ~w[ChatMessage Question Answer Audio Event]
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
def persist(%{"type" => type} = object, meta) when type in @object_types do
with {:ok, object} <- Object.create(object) do
@@ -111,7 +101,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
local: local,
recipients: recipients,
actor: object["actor"]
- }) do
+ }),
+ # TODO: add tests for expired activities, when Note type will be supported in new pipeline
+ {:ok, _} <- maybe_create_activity_expiration(activity) do
{:ok, activity, meta}
end
end
@@ -120,23 +112,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map, fake),
- true <- bypass_actor_check || check_actor_is_active(map["actor"]),
- {_, true} <- {:remote_limit_error, check_remote_limit(map)},
+ {_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])},
+ {_, true} <- {:remote_limit_pass, check_remote_limit(map)},
{:ok, map} <- MRF.filter(map),
{recipients, _, _} = get_recipients(map),
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
{:containment, :ok} <- {:containment, Containment.contain_child(map)},
- {:ok, map, object} <- insert_full_object(map) do
- {:ok, activity} =
- %Activity{
- data: map,
- local: local,
- actor: map["actor"],
- recipients: recipients
- }
- |> Repo.insert()
- |> maybe_create_activity_expiration()
-
+ {:ok, map, object} <- insert_full_object(map),
+ {:ok, activity} <- insert_activity_with_expiration(map, local, recipients) do
# Splice in the child object if we have one.
activity = Maps.put_if_present(activity, :object, object)
@@ -147,6 +130,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
%Activity{} = activity ->
{:ok, activity}
+ {:actor_check, _} ->
+ {:error, false}
+
+ {:containment, _} = error ->
+ error
+
+ {:error, _} = error ->
+ error
+
{:fake, true, map, recipients} ->
activity = %Activity{
data: map,
@@ -159,8 +151,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
{:ok, activity}
- error ->
- {:error, error}
+ {:remote_limit_pass, _} ->
+ {:error, :remote_limit}
+
+ {:reject, reason} ->
+ {:error, reason}
+ end
+ end
+
+ defp insert_activity_with_expiration(data, local, recipients) do
+ struct = %Activity{
+ data: data,
+ local: local,
+ actor: data["actor"],
+ recipients: recipients
+ }
+
+ with {:ok, activity} <- Repo.insert(struct) do
+ maybe_create_activity_expiration(activity)
end
end
@@ -173,13 +181,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
stream_out_participations(participations)
end
- defp maybe_create_activity_expiration({:ok, %{data: %{"expires_at" => expires_at}} = activity}) do
- with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do
+ defp maybe_create_activity_expiration(
+ %{data: %{"expires_at" => %DateTime{} = expires_at}} = activity
+ ) do
+ with {:ok, _job} <-
+ Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+ activity_id: activity.id,
+ expires_at: expires_at
+ }) do
{:ok, activity}
end
end
- defp maybe_create_activity_expiration(result), do: result
+ defp maybe_create_activity_expiration(activity), do: {:ok, activity}
defp create_or_bump_conversation(activity, actor) do
with {:ok, conversation} <- Conversation.create_or_bump_for(activity),
@@ -210,7 +224,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
conversation = Repo.preload(conversation, :participations)
last_activity_id =
- fetch_latest_activity_id_for_context(conversation.ap_id, %{
+ fetch_latest_direct_activity_id_for_context(conversation.ap_id, %{
user: user,
blocking_user: user
})
@@ -257,7 +271,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
with {: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),
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
_ <- notify_and_stream(activity),
@@ -295,76 +308,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- @spec accept(map()) :: {:ok, Activity.t()} | {:error, any()}
- def accept(params) do
- accept_or_reject("Accept", params)
- end
-
- @spec reject(map()) :: {:ok, Activity.t()} | {:error, any()}
- def reject(params) do
- accept_or_reject("Reject", params)
- end
-
- @spec accept_or_reject(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
- defp accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
- local = Map.get(params, :local, true)
- activity_id = Map.get(params, :activity_id, nil)
-
- data =
- %{"to" => to, "type" => type, "actor" => actor.ap_id, "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 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 +331,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 +411,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(
@@ -517,11 +434,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Repo.all()
end
- @spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
+ @spec fetch_latest_direct_activity_id_for_context(String.t(), keyword() | map()) ::
FlakeId.Ecto.CompatType.t() | nil
- def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
+ def fetch_latest_direct_activity_id_for_context(context, opts \\ %{}) do
context
|> fetch_activities_for_context_query(Map.merge(%{skip_preload: true}, opts))
+ |> restrict_visibility(%{visibility: "direct"})
|> limit(1)
|> select([a], a.id)
|> Repo.one()
@@ -833,7 +751,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_media(query, %{only_media: true}) do
from(
- [_activity, object] in query,
+ [activity, object] in query,
+ where: fragment("(?)->>'type' = ?", activity.data, "Create"),
where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
)
end
@@ -1008,6 +927,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
@@ -1138,6 +1077,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)
@@ -1146,6 +1086,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)
@@ -1271,6 +1212,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
@@ -1304,12 +1247,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
name: data["name"],
follower_address: data["followers"],
following_address: data["following"],
- bio: data["summary"],
+ bio: data["summary"] || "",
actor_type: actor_type,
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
@@ -1412,12 +1356,40 @@ 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
+ with nickname when is_binary(nickname) <- data[:nickname],
+ %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 #{data[: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)
@@ -1430,6 +1402,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 e2a5fb9e9..27bf6347d 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -401,21 +401,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
defp handle_user_activity(
%User{} = user,
- %{"type" => "Create", "object" => %{"type" => "Note"}} = params
+ %{"type" => "Create", "object" => %{"type" => "Note"} = object} = params
) do
- object =
- params["object"]
- |> Map.merge(Map.take(params, ["to", "cc"]))
- |> Map.put("attributedTo", user.ap_id())
- |> Transmogrifier.fix_object()
-
- ActivityPub.create(%{
- to: params["to"],
- actor: user,
- context: object["context"],
- object: object,
- additional: Map.take(params, ["cc"])
- })
+ content = if is_binary(object["content"]), do: object["content"], else: ""
+ name = if is_binary(object["name"]), do: object["name"], else: ""
+ summary = if is_binary(object["summary"]), do: object["summary"], else: ""
+ length = String.length(content <> name <> summary)
+
+ if length > Pleroma.Config.get([:instance, :limit]) do
+ {:error, dgettext("errors", "Note is over the character limit")}
+ else
+ object =
+ object
+ |> Map.merge(Map.take(params, ["to", "cc"]))
+ |> Map.put("attributedTo", user.ap_id())
+ |> Transmogrifier.fix_object()
+
+ ActivityPub.create(%{
+ to: params["to"],
+ actor: user,
+ context: object["context"],
+ object: object,
+ additional: Map.take(params, ["cc"])
+ })
+ end
end
defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
@@ -516,7 +525,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>
@@ -527,6 +535,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..9a7b7d9de 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -14,6 +14,41 @@ defmodule Pleroma.Web.ActivityPub.Builder do
require Pleroma.Constants
+ def accept_or_reject(actor, activity, type) do
+ data = %{
+ "id" => Utils.generate_activity_id(),
+ "actor" => actor.ap_id,
+ "type" => type,
+ "object" => activity.data["id"],
+ "to" => [activity.actor]
+ }
+
+ {:ok, data, []}
+ end
+
+ @spec reject(User.t(), Activity.t()) :: {:ok, map(), keyword()}
+ def reject(actor, rejected_activity) do
+ accept_or_reject(actor, rejected_activity, "Reject")
+ end
+
+ @spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()}
+ def accept(actor, accepted_activity) do
+ accept_or_reject(actor, accepted_activity, "Accept")
+ end
+
+ @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
@@ -67,6 +102,13 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
def create(actor, object, recipients) do
+ context =
+ if is_map(object) do
+ object["context"]
+ else
+ nil
+ end
+
{:ok,
%{
"id" => Utils.generate_activity_id(),
@@ -75,7 +117,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do
"object" => object,
"type" => "Create",
"published" => DateTime.utc_now() |> DateTime.to_iso8601()
- }, []}
+ }
+ |> Pleroma.Maps.put_if_present("context", context), []}
end
def chat_message(actor, recipient, content, opts \\ []) do
@@ -102,6 +145,22 @@ defmodule Pleroma.Web.ActivityPub.Builder do
end
end
+ def answer(user, object, name) do
+ {:ok,
+ %{
+ "type" => "Answer",
+ "actor" => user.ap_id,
+ "attributedTo" => user.ap_id,
+ "cc" => [object.data["actor"]],
+ "to" => [],
+ "name" => name,
+ "inReplyTo" => object.data["id"],
+ "context" => object.data["context"],
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "id" => Utils.generate_object_id()
+ }, []}
+ end
+
@spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
def tombstone(actor, id) do
{:ok,
@@ -123,13 +182,40 @@ 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)
to =
cond do
- actor.ap_id == Relay.relay_ap_id() ->
+ actor.ap_id == Relay.ap_id() ->
[actor.follower_address]
public? ->
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/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
index 8e47f1e02..bee47b4ed 100644
--- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
@@ -21,8 +21,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
@impl true
def describe, do: {:ok, %{}}
- defp local?(%{"id" => id}) do
- String.starts_with?(id, Pleroma.Web.Endpoint.url())
+ defp local?(%{"actor" => actor}) do
+ String.starts_with?(actor, Pleroma.Web.Endpoint.url())
end
defp note?(activity) do
@@ -31,10 +31,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
defp maybe_add_expiration(activity) do
days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
- expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: days)
+ expires_at = DateTime.utc_now() |> Timex.shift(days: days)
with %{"expires_at" => existing_expires_at} <- activity,
- :lt <- NaiveDateTime.compare(existing_expires_at, expires_at) do
+ :lt <- DateTime.compare(existing_expires_at, expires_at) do
activity
else
_ -> Map.put(activity, "expires_at", expires_at)
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/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
index 2627a0007..3bf70b894 100644
--- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
+++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
@@ -27,7 +27,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
def filter_by_summary(_in_reply_to, child), do: child
- def filter(%{"type" => "Create", "object" => child_object} = object) do
+ def filter(%{"type" => "Create", "object" => child_object} = object)
+ when is_map(child_object) do
child =
child_object["inReplyTo"]
|> Object.normalize(child_object["inReplyTo"])
diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
new file mode 100644
index 000000000..ea9c3d3f5
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
@@ -0,0 +1,56 @@
+# 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.MRF.ForceBotUnlistedPolicy do
+ alias Pleroma.User
+ @behaviour Pleroma.Web.ActivityPub.MRF
+ @moduledoc "Remove bot posts from federated timeline"
+
+ require Pleroma.Constants
+
+ defp check_by_actor_type(user), do: user.actor_type in ["Application", "Service"]
+ defp check_by_nickname(user), do: Regex.match?(~r/bot@|ebooks@/i, user.nickname)
+
+ defp check_if_bot(user), do: check_by_actor_type(user) or check_by_nickname(user)
+
+ @impl true
+ def filter(
+ %{
+ "type" => "Create",
+ "to" => to,
+ "cc" => cc,
+ "actor" => actor,
+ "object" => object
+ } = message
+ ) do
+ user = User.get_cached_by_ap_id(actor)
+ isbot = check_if_bot(user)
+
+ if isbot and Enum.member?(to, Pleroma.Constants.as_public()) do
+ to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address]
+ cc = List.delete(cc, user.follower_address) ++ [Pleroma.Constants.as_public()]
+
+ object =
+ object
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+
+ message =
+ message
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+ |> Map.put("object", object)
+
+ {:ok, message}
+ else
+ {:ok, message}
+ end
+ end
+
+ @impl true
+ def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
index 1764bc789..9ba07b4e3 100644
--- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
@@ -13,8 +13,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
defp delist_message(message, threshold) when threshold > 0 do
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
+ to = message["to"] || []
+ cc = message["cc"] || []
- follower_collection? = Enum.member?(message["to"] ++ message["cc"], follower_collection)
+ follower_collection? = Enum.member?(to ++ cc, follower_collection)
message =
case get_recipient_count(message) do
@@ -41,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
@@ -71,7 +73,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
end
@impl true
- def filter(%{"type" => "Create"} = message) do
+ def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = message)
+ when object_type in ~w{Note Article} do
reject_threshold =
Pleroma.Config.get(
[:mrf_hellthread, :reject_threshold],
@@ -84,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/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
index dfab105a3..98d595469 100644
--- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
@@ -13,22 +13,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
require Logger
@options [
- pool: :media
+ pool: :media,
+ recv_timeout: 10_000
]
def perform(:prefetch, url) do
Logger.debug("Prefetching #{inspect(url)}")
- opts =
- if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
- Keyword.put(@options, :recv_timeout, 10_000)
- else
- @options
- end
-
url
|> MediaProxy.url()
- |> HTTP.get([], adapter: opts)
+ |> HTTP.get([], @options)
end
def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do
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..d45d2d7e3 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
@@ -37,8 +37,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
defp check_delist(message, actions) do
if :delist in actions do
with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do
- to = List.delete(message["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
- cc = List.delete(message["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
+ to =
+ List.delete(message["to"] || [], Pleroma.Constants.as_public()) ++
+ [user.follower_address]
+
+ cc =
+ List.delete(message["cc"] || [], user.follower_address) ++
+ [Pleroma.Constants.as_public()]
message =
message
@@ -47,9 +52,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}
@@ -59,8 +63,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
defp check_strip_followers(message, actions) do
if :strip_followers in actions do
with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do
- to = List.delete(message["to"], user.follower_address)
- cc = List.delete(message["cc"], user.follower_address)
+ to = List.delete(message["to"] || [], user.follower_address)
+ cc = List.delete(message["cc"] || [], user.follower_address)
message =
message
@@ -69,9 +73,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 +101,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..bb193475a 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -3,33 +3,36 @@
# 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.FollowingRelationship
+ 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 +44,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 +68,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 +88,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 =
@@ -106,13 +109,42 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, object}
end
+ defp intersection(list1, list2) do
+ list1 -- list1 -- list2
+ end
+
+ defp check_followers_only(%{host: actor_host} = _actor_info, object) do
+ followers_only =
+ Config.get([:mrf_simple, :followers_only])
+ |> MRF.subdomains_regex()
+
+ object =
+ with true <- MRF.subdomain_match?(followers_only, actor_host),
+ user <- User.get_cached_by_ap_id(object["actor"]) do
+ # Don't use Map.get/3 intentionally, these must not be nil
+ fixed_to = object["to"] || []
+ fixed_cc = object["cc"] || []
+
+ to = FollowingRelationship.followers_ap_ids(user, fixed_to)
+ cc = FollowingRelationship.followers_ap_ids(user, fixed_cc)
+
+ object
+ |> Map.put("to", intersection([user.follower_address | to], fixed_to))
+ |> Map.put("cc", intersection([user.follower_address | cc], fixed_cc))
+ else
+ _ -> object
+ end
+
+ {:ok, object}
+ end
+
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 +154,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 +168,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 +185,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
@@ -172,10 +204,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{:ok, object} <- check_media_removal(actor_info, object),
{:ok, object} <- check_media_nsfw(actor_info, object),
{:ok, object} <- check_ftl_removal(actor_info, object),
+ {:ok, object} <- check_followers_only(actor_info, object),
{: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 +224,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 +234,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 c01c5f780..b77c06395 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -9,26 +9,102 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
the system.
"""
+ alias Pleroma.Activity
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+ alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
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" => type} = object, meta)
+ when type in ~w[Accept Reject] do
+ with {:ok, object} <-
+ object
+ |> AcceptRejectValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ def validate(%{"type" => "Event"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> EventValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ 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
|> UndoValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
+ undone_object = Activity.get_by_ap_id(object["object"])
+
+ meta =
+ meta
+ |> Keyword.put(:object_data, undone_object.data)
+
{:ok, object, meta}
end
end
@@ -63,17 +139,50 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
+ def validate(%{"type" => "Question"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> QuestionValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ def validate(%{"type" => "Audio"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> AudioValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ def validate(%{"type" => "Answer"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> AnswerValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
def validate(%{"type" => "EmojiReact"} = object, meta) do
with {:ok, object} <-
object
|> EmojiReactValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object |> Map.from_struct())
+ object = stringify_keys(object)
{:ok, object, meta}
end
end
- def validate(%{"type" => "Create", "object" => object} = create_activity, meta) do
+ def validate(
+ %{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object} = create_activity,
+ meta
+ ) do
with {:ok, object_data} <- cast_and_apply(object),
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
{:ok, create_activity} <-
@@ -85,12 +194,28 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
+ def validate(
+ %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
+ meta
+ )
+ when objtype in ~w[Question Answer Audio Event] do
+ with {:ok, object_data} <- cast_and_apply(object),
+ meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
+ {:ok, create_activity} <-
+ create_activity
+ |> CreateGenericValidator.cast_and_validate(meta)
+ |> Ecto.Changeset.apply_action(:insert) do
+ create_activity = stringify_keys(create_activity)
+ {:ok, create_activity, meta}
+ end
+ end
+
def validate(%{"type" => "Announce"} = object, meta) do
with {:ok, object} <-
object
|> AnnounceValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
- object = stringify_keys(object |> Map.from_struct())
+ object = stringify_keys(object)
{:ok, object, meta}
end
end
@@ -99,8 +224,25 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
ChatMessageValidator.cast_and_apply(object)
end
+ def cast_and_apply(%{"type" => "Question"} = object) do
+ QuestionValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(%{"type" => "Answer"} = object) do
+ AnswerValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(%{"type" => "Audio"} = object) do
+ AudioValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(%{"type" => "Event"} = object) do
+ EventValidator.cast_and_apply(object)
+ end
+
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
+ # is_struct/1 isn't present in Elixir 1.8.x
def stringify_keys(%{__struct__: _} = object) do
object
|> Map.from_struct()
@@ -120,7 +262,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
def stringify_keys(object), do: object
def fetch_actor(object) do
- with {:ok, actor} <- Types.ObjectID.cast(object["actor"]) do
+ with {:ok, actor} <- ObjectValidators.ObjectID.cast(object["actor"]) do
User.get_or_fetch_by_ap_id(actor)
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex
new file mode 100644
index 000000000..179beda58
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex
@@ -0,0 +1,56 @@
+# 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.AcceptRejectValidator do
+ use Ecto.Schema
+
+ alias Pleroma.Activity
+ 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(:object, ObjectValidators.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ 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, ["Accept", "Reject"])
+ |> validate_actor_presence()
+ |> validate_object_presence(allowed_types: ["Follow"])
+ |> validate_accept_reject_rights()
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data
+ |> validate_data
+ end
+
+ def validate_accept_reject_rights(cng) do
+ with object_id when is_binary(object_id) <- get_field(cng, :object),
+ %Activity{data: %{"object" => followed_actor}} <- Activity.get_by_ap_id(object_id),
+ true <- followed_actor == get_field(cng, :actor) do
+ cng
+ else
+ _e ->
+ cng
+ |> add_error(:actor, "can't accept or reject the given activity")
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
index 40f861f47..6f757f49c 100644
--- a/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/announce_validator.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
@@ -19,14 +19,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
- field(:object, Types.ObjectID)
- field(:actor, Types.ObjectID)
+ field(:object, ObjectValidators.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
field(:context, :string, autogenerate: {Utils, :generate_context_id, []})
- field(:to, Types.Recipients, default: [])
- field(:cc, Types.Recipients, default: [])
- field(:published, Types.DateTime)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:published, ObjectValidators.DateTime)
end
def cast_and_validate(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
new file mode 100644
index 000000000..b9fbaf4f6
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/answer_validator.ex
@@ -0,0 +1,62 @@
+# 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.AnswerValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ import Ecto.Changeset
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
+ field(:type, :string)
+ field(:name, :string)
+ field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:attributedTo, ObjectValidators.ObjectID)
+
+ # TODO: Remove actor on objects
+ field(:actor, ObjectValidators.ObjectID)
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data()
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ def changeset(struct, data) do
+ struct
+ |> cast(data, __schema__(:fields))
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Answer"])
+ |> validate_required([:id, :inReplyTo, :name, :attributedTo, :actor])
+ |> CommonValidations.validate_any_presence([:cc, :to])
+ |> CommonValidations.validate_fields_match([:actor, :attributedTo])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_host_match()
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
index f53bb02be..c8b148280 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -41,34 +41,34 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
end
def fix_media_type(data) do
- data =
- data
- |> Map.put_new("mediaType", data["mimeType"])
+ data = Map.put_new(data, "mediaType", data["mimeType"])
if MIME.valid?(data["mediaType"]) do
data
else
- data
- |> Map.put("mediaType", "application/octet-stream")
+ Map.put(data, "mediaType", "application/octet-stream")
end
end
- def fix_url(data) do
- case data["url"] do
- url when is_binary(url) ->
- data
- |> Map.put(
- "url",
- [
- %{
- "href" => url,
- "type" => "Link",
- "mediaType" => data["mediaType"]
- }
- ]
- )
-
- _ ->
+ defp handle_href(href, mediaType) do
+ [
+ %{
+ "href" => href,
+ "type" => "Link",
+ "mediaType" => mediaType
+ }
+ ]
+ end
+
+ defp fix_url(data) do
+ cond do
+ is_binary(data["url"]) ->
+ Map.put(data, "url", handle_href(data["url"], data["mediaType"]))
+
+ is_binary(data["href"]) and data["url"] == nil ->
+ Map.put(data, "url", handle_href(data["href"], data["mediaType"]))
+
+ true ->
data
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex
new file mode 100644
index 000000000..1a97c504a
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex
@@ -0,0 +1,107 @@
+# 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.AudioValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ import Ecto.Changeset
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
+ # TODO: Write type
+ field(:tag, {:array, :map}, default: [])
+ field(:type, :string)
+ field(:content, :string)
+ field(:context, :string)
+
+ # TODO: Remove actor on objects
+ field(:actor, ObjectValidators.ObjectID)
+
+ field(:attributedTo, ObjectValidators.ObjectID)
+ field(:summary, :string)
+ field(:published, ObjectValidators.DateTime)
+ field(:emoji, ObjectValidators.Emoji, default: %{})
+ field(:sensitive, :boolean, default: false)
+ embeds_many(:attachment, AttachmentValidator)
+ field(:replies_count, :integer, default: 0)
+ field(:like_count, :integer, default: 0)
+ field(:announcement_count, :integer, default: 0)
+ field(:inReplyTo, :string)
+ field(:url, ObjectValidators.Uri)
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
+
+ field(:likes, {:array, :string}, default: [])
+ field(:announcements, {:array, :string}, default: [])
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ defp fix_url(%{"url" => url} = data) when is_list(url) do
+ attachment =
+ Enum.find(url, fn x -> is_map(x) and String.starts_with?(x["mimeType"], "audio/") end)
+
+ link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
+
+ data
+ |> Map.put("attachment", [attachment])
+ |> Map.put("url", link_element["href"])
+ end
+
+ defp fix_url(data), do: data
+
+ defp fix(data) do
+ data
+ |> CommonFixes.fix_defaults()
+ |> CommonFixes.fix_attribution()
+ |> Transmogrifier.fix_emoji()
+ |> fix_url()
+ end
+
+ def changeset(struct, data) do
+ data = fix(data)
+
+ struct
+ |> cast(data, __schema__(:fields) -- [:attachment])
+ |> cast_embed(:attachment)
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Audio"])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
+ |> CommonValidations.validate_any_presence([:cc, :to])
+ |> CommonValidations.validate_fields_match([:actor, :attributedTo])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_host_match()
+ end
+end
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 138736f23..6acd4a771 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
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
import Ecto.Changeset
import Pleroma.Web.ActivityPub.Transmogrifier, only: [fix_emoji: 1]
@@ -16,13 +16,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
@derive Jason.Encoder
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
- field(:to, Types.Recipients, default: [])
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
field(:type, :string)
- field(:content, Types.SafeText)
- field(:actor, Types.ObjectID)
- field(:published, Types.DateTime)
- field(:emoji, :map, default: %{})
+ field(:content, ObjectValidators.SafeText)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:published, ObjectValidators.DateTime)
+ field(:emoji, ObjectValidators.Emoji, default: %{})
embeds_one(:attachment, AttachmentValidator)
end
@@ -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/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
new file mode 100644
index 000000000..720213d73
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -0,0 +1,22 @@
+# 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.CommonFixes do
+ alias Pleroma.Web.ActivityPub.Utils
+
+ # based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults
+ def fix_defaults(data) do
+ %{data: %{"id" => context}, id: context_id} =
+ Utils.create_context(data["context"] || data["conversation"])
+
+ data
+ |> Map.put("context", context)
+ |> Map.put("context_id", context_id)
+ end
+
+ def fix_attribution(data) do
+ data
+ |> Map.put_new("actor", data["attributedTo"])
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
index aeef31945..603d87b8e 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
alias Pleroma.Object
alias Pleroma.User
- def validate_recipients_presence(cng, fields \\ [:to, :cc]) do
+ def validate_any_presence(cng, fields) do
non_empty =
fields
|> Enum.map(fn field -> get_field(cng, field) end)
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
fields
|> Enum.reduce(cng, fn field, cng ->
cng
- |> add_error(field, "no recipients in any field")
+ |> add_error(field, "none of #{inspect(fields)} present")
end)
end
end
@@ -34,10 +34,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
cng
|> validate_change(field_name, fn field_name, actor ->
- if User.get_cached_by_ap_id(actor) do
- []
- else
- [{field_name, "can't find user"}]
+ case User.get_cached_by_ap_id(actor) do
+ %User{deactivated: true} ->
+ [{field_name, "user is deactivated"}]
+
+ %User{} ->
+ []
+
+ _ ->
+ [{field_name, "can't find user"}]
end
end)
end
@@ -77,4 +82,60 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
if actor_cng.valid?, do: actor_cng, else: object_cng
end
+
+ def validate_host_match(cng, fields \\ [:id, :actor]) do
+ if same_domain?(cng, fields) do
+ cng
+ else
+ fields
+ |> Enum.reduce(cng, fn field, cng ->
+ cng
+ |> add_error(field, "hosts of #{inspect(fields)} aren't matching")
+ end)
+ end
+ end
+
+ def validate_fields_match(cng, fields) do
+ if map_unique?(cng, fields) do
+ cng
+ else
+ fields
+ |> Enum.reduce(cng, fn field, cng ->
+ cng
+ |> add_error(field, "Fields #{inspect(fields)} aren't matching")
+ end)
+ end
+ end
+
+ defp map_unique?(cng, fields, func \\ & &1) do
+ Enum.reduce_while(fields, nil, fn field, acc ->
+ value =
+ cng
+ |> get_field(field)
+ |> func.()
+
+ case {value, acc} do
+ {value, nil} -> {:cont, value}
+ {value, value} -> {:cont, value}
+ _ -> {:halt, false}
+ end
+ end)
+ end
+
+ def same_domain?(cng, fields \\ [:actor, :object]) do
+ map_unique?(cng, fields, fn value -> URI.parse(value).host end)
+ end
+
+ # This figures out if a user is able to create, delete or modify something
+ # based on the domain and superuser status
+ def validate_modification_rights(cng) do
+ actor = User.get_cached_by_ap_id(get_field(cng, :actor))
+
+ if User.superuser?(actor) || same_domain?(cng) do
+ cng
+ else
+ cng
+ |> add_error(:actor, "is not allowed to modify object")
+ end
+ end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
index fc582400b..7269f9ff0 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_chat_message_validator.ex
@@ -7,9 +7,9 @@
# - doesn't embed, will only get the object id
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -17,11 +17,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
- field(:actor, Types.ObjectID)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:actor, ObjectValidators.ObjectID)
field(:type, :string)
- field(:to, Types.Recipients, default: [])
- field(:object, Types.ObjectID)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:object, ObjectValidators.ObjectID)
end
def cast_and_apply(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
new file mode 100644
index 000000000..b3dbeea57
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
@@ -0,0 +1,144 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+# Code based on CreateChatMessageValidator
+# NOTES
+# - doesn't embed, will only get the object id
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Object
+
+ import Ecto.Changeset
+ import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:type, :string)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:object, ObjectValidators.ObjectID)
+ field(:expires_at, ObjectValidators.DateTime)
+
+ # Should be moved to object, done for CommonAPI.Utils.make_context
+ field(:context, :string)
+ end
+
+ def cast_data(data, meta \\ []) do
+ data = fix(data, meta)
+
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data, meta \\ []) do
+ data
+ |> cast_data(meta)
+ |> validate_data(meta)
+ end
+
+ def changeset(struct, data) do
+ struct
+ |> cast(data, __schema__(:fields))
+ end
+
+ defp fix_context(data, meta) do
+ if object = meta[:object_data] do
+ Map.put_new(data, "context", object["context"])
+ else
+ data
+ end
+ end
+
+ defp fix_addressing(data, meta) do
+ if object = meta[:object_data] do
+ data
+ |> Map.put_new("to", object["to"] || [])
+ |> Map.put_new("cc", object["cc"] || [])
+ else
+ data
+ end
+ end
+
+ defp fix(data, meta) do
+ data
+ |> fix_context(meta)
+ |> fix_addressing(meta)
+ end
+
+ def validate_data(cng, meta \\ []) do
+ cng
+ |> validate_required([:actor, :type, :object])
+ |> validate_inclusion(:type, ["Create"])
+ |> validate_actor_presence()
+ |> validate_any_presence([:to, :cc])
+ |> validate_actors_match(meta)
+ |> validate_context_match(meta)
+ |> validate_object_nonexistence()
+ |> validate_object_containment()
+ end
+
+ def validate_object_containment(cng) do
+ actor = get_field(cng, :actor)
+
+ cng
+ |> validate_change(:object, fn :object, object_id ->
+ %URI{host: object_id_host} = URI.parse(object_id)
+ %URI{host: actor_host} = URI.parse(actor)
+
+ if object_id_host == actor_host do
+ []
+ else
+ [{:object, "The host of the object id doesn't match with the host of the actor"}]
+ end
+ end)
+ end
+
+ def validate_object_nonexistence(cng) do
+ cng
+ |> validate_change(:object, fn :object, object_id ->
+ if Object.get_cached_by_ap_id(object_id) do
+ [{:object, "The object to create already exists"}]
+ else
+ []
+ end
+ end)
+ end
+
+ def validate_actors_match(cng, meta) do
+ attributed_to = meta[:object_data]["attributedTo"] || meta[:object_data]["actor"]
+
+ cng
+ |> validate_change(:actor, fn :actor, actor ->
+ if actor == attributed_to do
+ []
+ else
+ [{:actor, "Actor doesn't match with object attributedTo"}]
+ end
+ end)
+ end
+
+ def validate_context_match(cng, %{object_data: %{"context" => object_context}}) do
+ cng
+ |> validate_change(:context, fn :context, context ->
+ if context == object_context do
+ []
+ else
+ [{:context, "context field not matching between Create and object (#{object_context})"}]
+ end
+ end)
+ end
+
+ def validate_context_match(cng, _), do: cng
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
index 926804ce7..9b9743c4a 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_note_validator.ex
@@ -5,22 +5,21 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
import Ecto.Changeset
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
- field(:actor, Types.ObjectID)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:actor, ObjectValidators.ObjectID)
field(:type, :string)
- field(:to, {:array, :string})
- field(:cc, {:array, :string})
- field(:bto, {:array, :string}, default: [])
- field(:bcc, {:array, :string}, default: [])
-
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
embeds_one(:object, NoteValidator)
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
index e5d08eb5c..2634e8d4d 100644
--- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
@@ -6,8 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
use Ecto.Schema
alias Pleroma.Activity
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -15,13 +14,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
- field(:actor, Types.ObjectID)
- field(:to, Types.Recipients, default: [])
- field(:cc, Types.Recipients, default: [])
- field(:deleted_activity_id, Types.ObjectID)
- field(:object, Types.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:deleted_activity_id, ObjectValidators.ObjectID)
+ field(:object, ObjectValidators.ObjectID)
end
def cast_data(data) do
@@ -59,7 +58,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Delete"])
|> validate_actor_presence()
- |> validate_deletion_rights()
+ |> validate_modification_rights()
|> validate_object_or_user_presence(allowed_types: @deletable_types)
|> add_deleted_activity_id()
end
@@ -68,31 +67,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
!same_domain?(cng)
end
- defp same_domain?(cng) do
- actor_uri =
- cng
- |> get_field(:actor)
- |> URI.parse()
-
- object_uri =
- cng
- |> get_field(:object)
- |> URI.parse()
-
- object_uri.host == actor_uri.host
- end
-
- def validate_deletion_rights(cng) do
- actor = User.get_cached_by_ap_id(get_field(cng, :actor))
-
- if User.superuser?(actor) || same_domain?(cng) do
- cng
- else
- cng
- |> add_error(:actor, "is not allowed to delete object")
- end
- end
-
def cast_and_validate(data) do
data
|> cast_data
diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
index e87519c59..336c92d35 100644
--- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
@@ -5,8 +5,8 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -14,14 +14,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
- field(:object, Types.ObjectID)
- field(:actor, Types.ObjectID)
+ field(:object, ObjectValidators.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
field(:context, :string)
field(:content, :string)
- field(:to, {:array, :string}, default: [])
- field(:cc, {:array, :string}, default: [])
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
end
def cast_and_validate(data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
new file mode 100644
index 000000000..0b4c99dc0
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
@@ -0,0 +1,97 @@
+# 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.EventValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ import Ecto.Changeset
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ # Extends from NoteValidator
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
+ # TODO: Write type
+ field(:tag, {:array, :map}, default: [])
+ field(:type, :string)
+
+ field(:name, :string)
+ field(:summary, :string)
+ field(:content, :string)
+
+ field(:context, :string)
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
+
+ # TODO: Remove actor on objects
+ field(:actor, ObjectValidators.ObjectID)
+
+ field(:attributedTo, ObjectValidators.ObjectID)
+ field(:published, ObjectValidators.DateTime)
+ field(:emoji, ObjectValidators.Emoji, default: %{})
+ field(:sensitive, :boolean, default: false)
+ embeds_many(:attachment, AttachmentValidator)
+ field(:replies_count, :integer, default: 0)
+ field(:like_count, :integer, default: 0)
+ field(:announcement_count, :integer, default: 0)
+ field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:url, ObjectValidators.Uri)
+
+ field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
+ field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ defp fix(data) do
+ data
+ |> CommonFixes.fix_defaults()
+ |> CommonFixes.fix_attribution()
+ |> Transmogrifier.fix_emoji()
+ end
+
+ def changeset(struct, data) do
+ data = fix(data)
+
+ struct
+ |> cast(data, __schema__(:fields) -- [:attachment])
+ |> cast_embed(:attachment)
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Event"])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
+ |> CommonValidations.validate_any_presence([:cc, :to])
+ |> CommonValidations.validate_fields_match([:actor, :attributedTo])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_host_match()
+ end
+end
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/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
index 034f25492..493e4c247 100644
--- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
@@ -5,8 +5,8 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
alias Pleroma.Web.ActivityPub.Utils
import Ecto.Changeset
@@ -15,13 +15,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
- field(:object, Types.ObjectID)
- field(:actor, Types.ObjectID)
+ field(:object, ObjectValidators.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
field(:context, :string)
- field(:to, Types.Recipients, default: [])
- field(:cc, Types.Recipients, default: [])
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
end
def cast_and_validate(data) do
@@ -67,7 +67,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
with {[], []} <- {to, cc},
%Object{data: %{"actor" => actor}} <- Object.get_cached_by_ap_id(object),
- {:ok, actor} <- Types.ObjectID.cast(actor) do
+ {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
cng
|> put_change(:to, [actor])
else
diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
index 462a5620a..ab4469a59 100644
--- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
@@ -5,44 +5,46 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
use Ecto.Schema
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
- field(:to, {:array, :string}, default: [])
- field(:cc, {:array, :string}, default: [])
- field(:bto, {:array, :string}, default: [])
- field(:bcc, {:array, :string}, default: [])
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
# TODO: Write type
field(:tag, {:array, :map}, default: [])
field(:type, :string)
+
+ field(:name, :string)
+ field(:summary, :string)
field(:content, :string)
+
field(:context, :string)
- field(:actor, Types.ObjectID)
- field(:attributedTo, Types.ObjectID)
- field(:summary, :string)
- field(:published, Types.DateTime)
- # TODO: Write type
- field(:emoji, :map, default: %{})
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
+
+ field(:actor, ObjectValidators.ObjectID)
+ field(:attributedTo, ObjectValidators.ObjectID)
+ field(:published, ObjectValidators.DateTime)
+ field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
# TODO: Write type
field(:attachment, {:array, :map}, default: [])
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
- field(:inRepyTo, :string)
- field(:uri, Types.Uri)
+ field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:url, ObjectValidators.Uri)
field(:likes, {:array, :string}, default: [])
field(:announcements, {:array, :string}, default: [])
-
- # see if needed
- field(:conversation, :string)
- field(:context_id, :string)
end
def cast_and_validate(data) do
@@ -51,7 +53,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
|> validate_data()
end
+ defp fix(data) do
+ data
+ |> Transmogrifier.fix_emoji()
+ end
+
def cast_data(data) do
+ data = fix(data)
+
%__MODULE__{}
|> cast(data, __schema__(:fields))
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
new file mode 100644
index 000000000..478b3b5cf
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex
@@ -0,0 +1,37 @@
+# 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.QuestionOptionsValidator do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+
+ @primary_key false
+
+ embedded_schema do
+ field(:name, :string)
+
+ embeds_one :replies, Replies, primary_key: false do
+ field(:totalItems, :integer)
+ field(:type, :string)
+ end
+
+ field(:type, :string)
+ end
+
+ def changeset(struct, data) do
+ struct
+ |> cast(data, [:name, :type])
+ |> cast_embed(:replies, with: &replies_changeset/2)
+ |> validate_inclusion(:type, ["Note"])
+ |> validate_required([:name, :type])
+ end
+
+ def replies_changeset(struct, data) do
+ struct
+ |> cast(data, [:totalItems, :type])
+ |> validate_inclusion(:type, ["Collection"])
+ |> validate_required([:type])
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
new file mode 100644
index 000000000..934d3c1ea
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
@@ -0,0 +1,112 @@
+# 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.QuestionValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+ alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ import Ecto.Changeset
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ # Extends from NoteValidator
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
+ # TODO: Write type
+ field(:tag, {:array, :map}, default: [])
+ field(:type, :string)
+ field(:content, :string)
+ field(:context, :string)
+
+ # TODO: Remove actor on objects
+ field(:actor, ObjectValidators.ObjectID)
+
+ field(:attributedTo, ObjectValidators.ObjectID)
+ field(:summary, :string)
+ field(:published, ObjectValidators.DateTime)
+ field(:emoji, ObjectValidators.Emoji, default: %{})
+ field(:sensitive, :boolean, default: false)
+ embeds_many(:attachment, AttachmentValidator)
+ field(:replies_count, :integer, default: 0)
+ field(:like_count, :integer, default: 0)
+ field(:announcement_count, :integer, default: 0)
+ field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:url, ObjectValidators.Uri)
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
+
+ field(:likes, {:array, :string}, default: [])
+ field(:announcements, {:array, :string}, default: [])
+
+ field(:closed, ObjectValidators.DateTime)
+ field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
+ embeds_many(:anyOf, QuestionOptionsValidator)
+ embeds_many(:oneOf, QuestionOptionsValidator)
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ defp fix_closed(data) do
+ cond do
+ is_binary(data["closed"]) -> data
+ is_binary(data["endTime"]) -> Map.put(data, "closed", data["endTime"])
+ true -> Map.drop(data, ["closed"])
+ end
+ end
+
+ defp fix(data) do
+ data
+ |> CommonFixes.fix_defaults()
+ |> CommonFixes.fix_attribution()
+ |> Transmogrifier.fix_emoji()
+ |> fix_closed()
+ end
+
+ def changeset(struct, data) do
+ data = fix(data)
+
+ struct
+ |> cast(data, __schema__(:fields) -- [:anyOf, :oneOf, :attachment])
+ |> cast_embed(:attachment)
+ |> cast_embed(:anyOf)
+ |> cast_embed(:oneOf)
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Question"])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
+ |> CommonValidations.validate_any_presence([:cc, :to])
+ |> CommonValidations.validate_fields_match([:actor, :attributedTo])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_any_presence([:oneOf, :anyOf])
+ |> CommonValidations.validate_host_match()
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex b/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex
deleted file mode 100644
index 4f412fcde..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/types/date_time.ex
+++ /dev/null
@@ -1,34 +0,0 @@
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime do
- @moduledoc """
- The AP standard defines the date fields in AP as xsd:DateTime. Elixir's
- DateTime can't parse this, but it can parse the related iso8601. This
- module punches the date until it looks like iso8601 and normalizes to
- it.
-
- DateTimes without a timezone offset are treated as UTC.
-
- Reference: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-published
- """
- use Ecto.Type
-
- def type, do: :string
-
- def cast(datetime) when is_binary(datetime) do
- with {:ok, datetime, _} <- DateTime.from_iso8601(datetime) do
- {:ok, DateTime.to_iso8601(datetime)}
- else
- {:error, :missing_offset} -> cast("#{datetime}Z")
- _e -> :error
- end
- end
-
- def cast(_), do: :error
-
- def dump(data) do
- {:ok, data}
- end
-
- def load(data) do
- {:ok, data}
- end
-end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex b/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex
deleted file mode 100644
index f71f76370..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/types/object_id.ex
+++ /dev/null
@@ -1,23 +0,0 @@
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do
- use Ecto.Type
-
- def type, do: :string
-
- def cast(object) when is_binary(object) do
- # Host has to be present and scheme has to be an http scheme (for now)
- case URI.parse(object) do
- %URI{host: nil} -> :error
- %URI{host: ""} -> :error
- %URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, object}
- _ -> :error
- end
- end
-
- def cast(%{"id" => object}), do: cast(object)
-
- def cast(_), do: :error
-
- def dump(data), do: {:ok, data}
-
- def load(data), do: {:ok, data}
-end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/recipients.ex b/lib/pleroma/web/activity_pub/object_validators/types/recipients.ex
deleted file mode 100644
index 408e0f6ee..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/types/recipients.ex
+++ /dev/null
@@ -1,36 +0,0 @@
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients do
- use Ecto.Type
-
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID
-
- def type, do: {:array, ObjectID}
-
- def cast(object) when is_binary(object) do
- cast([object])
- end
-
- def cast(data) when is_list(data) do
- data
- |> Enum.reduce_while({:ok, []}, fn element, {:ok, list} ->
- case ObjectID.cast(element) do
- {:ok, id} ->
- {:cont, {:ok, [id | list]}}
-
- _ ->
- {:halt, :error}
- end
- end)
- end
-
- def cast(_) do
- :error
- end
-
- def dump(data) do
- {:ok, data}
- end
-
- def load(data) do
- {:ok, data}
- end
-end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/safe_text.ex b/lib/pleroma/web/activity_pub/object_validators/types/safe_text.ex
deleted file mode 100644
index 95c948123..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/types/safe_text.ex
+++ /dev/null
@@ -1,25 +0,0 @@
-# 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.Types.SafeText do
- use Ecto.Type
-
- alias Pleroma.HTML
-
- def type, do: :string
-
- def cast(str) when is_binary(str) do
- {:ok, HTML.filter_tags(str)}
- end
-
- def cast(_), do: :error
-
- def dump(data) do
- {:ok, data}
- end
-
- def load(data) do
- {:ok, data}
- end
-end
diff --git a/lib/pleroma/web/activity_pub/object_validators/types/uri.ex b/lib/pleroma/web/activity_pub/object_validators/types/uri.ex
deleted file mode 100644
index 24845bcc0..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/types/uri.ex
+++ /dev/null
@@ -1,20 +0,0 @@
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.Uri do
- use Ecto.Type
-
- def type, do: :string
-
- def cast(uri) when is_binary(uri) do
- case URI.parse(uri) do
- %URI{host: nil} -> :error
- %URI{host: ""} -> :error
- %URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, uri}
- _ -> :error
- end
- end
-
- def cast(_), do: :error
-
- def dump(data), do: {:ok, data}
-
- def load(data), do: {:ok, data}
-end
diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
index d0ba418e8..8cae94467 100644
--- a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
use Ecto.Schema
alias Pleroma.Activity
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@@ -14,12 +14,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
@primary_key false
embedded_schema do
- field(:id, Types.ObjectID, primary_key: true)
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
- field(:object, Types.ObjectID)
- field(:actor, Types.ObjectID)
- field(:to, {:array, :string}, default: [])
- field(:cc, {:array, :string}, default: [])
+ field(:object, ObjectValidators.ObjectID)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
end
def cast_and_validate(data) do
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/object_validators/url_object_validator.ex b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
index 47e231150..881030f38 100644
--- a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
@@ -1,15 +1,19 @@
+# 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.UrlObjectValidator do
use Ecto.Schema
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
@primary_key false
embedded_schema do
field(:type, :string)
- field(:href, Types.Uri)
- field(:mediaType, :string)
+ field(:href, ObjectValidators.Uri)
+ field(:mediaType, :string, default: "application/octet-stream")
end
def changeset(struct, data) do
diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex
index 6875c47f6..36e325c37 100644
--- a/lib/pleroma/web/activity_pub/pipeline.ex
+++ b/lib/pleroma/web/activity_pub/pipeline.ex
@@ -52,6 +52,13 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
do_not_federate = meta[:do_not_federate] || !Config.get([:instance, :federating])
if !do_not_federate && local do
+ activity =
+ if object = Keyword.get(meta, :object_data) do
+ %{activity | data: Map.put(activity.data, "object", object)}
+ else
+ activity
+ end
+
Federator.publish(activity)
{:ok, :federated}
else
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..b65710a94 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -10,25 +10,19 @@ defmodule Pleroma.Web.ActivityPub.Relay do
alias Pleroma.Web.CommonAPI
require Logger
- @relay_nickname "relay"
+ @nickname "relay"
- def get_actor do
- actor =
- relay_ap_id()
- |> User.get_or_create_service_actor_by_ap_id(@relay_nickname)
+ @spec ap_id() :: String.t()
+ def ap_id, do: "#{Pleroma.Web.Endpoint.url()}/#{@nickname}"
- actor
- end
-
- def relay_ap_id do
- "#{Pleroma.Web.Endpoint.url()}/relay"
- end
+ @spec get_actor() :: User.t() | nil
+ def get_actor, do: User.get_or_create_service_actor_by_ap_id(ap_id(), @nickname)
@spec follow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
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
@@ -61,34 +55,38 @@ defmodule Pleroma.Web.ActivityPub.Relay do
def publish(_), do: {:error, "Not implemented"}
- @spec list(boolean()) :: {:ok, [String.t()]} | {:error, any()}
- def list(with_not_accepted \\ false) do
+ @spec list() :: {:ok, [%{actor: String.t(), followed_back: boolean()}]} | {:error, any()}
+ def list do
with %User{} = user <- get_actor() do
accepted =
user
- |> User.following()
- |> Enum.map(fn entry -> URI.parse(entry).host end)
- |> Enum.uniq()
-
- list =
- if with_not_accepted do
- without_accept =
- user
- |> Pleroma.Activity.following_requests_for_actor()
- |> Enum.map(fn a -> URI.parse(a.data["object"]).host <> " (no Accept received)" end)
- |> Enum.uniq()
+ |> following()
+ |> Enum.map(fn actor -> %{actor: actor, followed_back: true} end)
- accepted ++ without_accept
- else
- accepted
- end
+ without_accept =
+ user
+ |> Pleroma.Activity.following_requests_for_actor()
+ |> Enum.map(fn activity -> %{actor: activity.data["object"], followed_back: false} end)
+ |> Enum.uniq()
- {:ok, list}
+ {:ok, accepted ++ without_accept}
else
error -> format_error(error)
end
end
+ @spec following() :: [String.t()]
+ def following do
+ get_actor()
+ |> following()
+ end
+
+ defp following(user) do
+ user
+ |> User.following_ap_ids()
+ |> Enum.uniq()
+ end
+
defp format_error({:error, error}), do: format_error(error)
defp format_error(error) do
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 1a1cc675c..46a8be767 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -6,20 +6,157 @@ 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
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Push
alias Pleroma.Web.Streamer
+ alias Pleroma.Workers.BackgroundWorker
+
+ require Logger
def handle(object, meta \\ [])
+ # Task this handles
+ # - Follows
+ # - Sends a notification
+ def handle(
+ %{
+ data: %{
+ "actor" => actor,
+ "type" => "Accept",
+ "object" => follow_activity_id
+ }
+ } = object,
+ meta
+ ) do
+ with %Activity{actor: follower_id} = follow_activity <-
+ Activity.get_by_ap_id(follow_activity_id),
+ %User{} = followed <- User.get_cached_by_ap_id(actor),
+ %User{} = follower <- User.get_cached_by_ap_id(follower_id),
+ {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
+ {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
+ Notification.update_notification_type(followed, follow_activity)
+ User.update_follower_count(followed)
+ User.update_following_count(follower)
+ end
+
+ {:ok, object, meta}
+ end
+
+ # Task this handles
+ # - Rejects all existing follow activities for this person
+ # - Updates the follow state
+ # - Dismisses notification
+ def handle(
+ %{
+ data: %{
+ "actor" => actor,
+ "type" => "Reject",
+ "object" => follow_activity_id
+ }
+ } = object,
+ meta
+ ) do
+ with %Activity{actor: follower_id} = follow_activity <-
+ Activity.get_by_ap_id(follow_activity_id),
+ %User{} = followed <- User.get_cached_by_ap_id(actor),
+ %User{} = follower <- User.get_cached_by_ap_id(follower_id),
+ {:ok, _follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject") do
+ FollowingRelationship.update(follower, followed, :follow_reject)
+ Notification.dismiss(follow_activity)
+ end
+
+ {:ok, object, meta}
+ end
+
+ # 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
+ {:ok, accept_data, _} = Builder.accept(followed, object)
+ {:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true)
+ end
+ else
+ {:following, {:error, _}, _follower, followed} ->
+ {:ok, reject_data, _} = Builder.reject(followed, object)
+ {:ok, _activity, _} = Pipeline.common_pipeline(reject_data, local: true)
+
+ _ ->
+ 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
@@ -35,10 +172,22 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# Tasks this handles
# - Actually create object
# - Rollback if we couldn't create it
+ # - Increase the user note count
+ # - Increase the reply count
+ # - Increase replies count
+ # - Set up ActivityExpiration
# - Set up notifications
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
- with {:ok, _object, meta} <- handle_object_creation(meta[:object_data], meta) do
+ with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta),
+ %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
+ {:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
+
+ if in_reply_to = object.data["inReplyTo"] do
+ Object.increase_replies_count(in_reply_to)
+ end
+
+ BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
meta =
meta
@@ -62,7 +211,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}
@@ -96,13 +248,15 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Stream out the activity
def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
deleted_object =
- Object.normalize(deleted_object, false) || User.get_cached_by_ap_id(deleted_object)
+ Object.normalize(deleted_object, false) ||
+ User.get_cached_by_ap_id(deleted_object)
result =
case deleted_object do
%Object{} ->
with {:ok, deleted_object, activity} <- Object.delete(deleted_object),
- %User{} = user <- User.get_cached_by_ap_id(deleted_object.data["actor"]) do
+ {_, actor} when is_binary(actor) <- {:actor, deleted_object.data["actor"]},
+ %User{} = user <- User.get_cached_by_ap_id(actor) do
User.remove_pinnned_activity(user, activity)
{:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object)
@@ -116,6 +270,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
ActivityPub.stream_out(object)
ActivityPub.stream_out_participations(deleted_object, user)
:ok
+ else
+ {:actor, _} ->
+ Logger.error("The object doesn't have an actor: #{inspect(deleted_object)}")
+ :no_object_actor
end
%User{} ->
@@ -165,19 +323,44 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
end
+ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
+ with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do
+ Object.increase_vote_count(
+ object.data["inReplyTo"],
+ object.data["name"],
+ object.data["actor"]
+ )
+
+ {:ok, object, meta}
+ end
+ end
+
+ def handle_object_creation(%{"type" => objtype} = object, meta)
+ when objtype in ~w[Audio Question Event] do
+ with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
+ {:ok, object, meta}
+ end
+ end
+
# Nothing to do
- def handle_object_creation(object) do
- {:ok, object}
+ def handle_object_creation(object, meta) do
+ {:ok, object, meta}
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 +390,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 985921aa0..af4384213 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -8,9 +8,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"""
alias Pleroma.Activity
alias Pleroma.EarmarkRenderer
- alias Pleroma.FollowingRelationship
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Maps
- alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Object.Containment
alias Pleroma.Repo
@@ -18,7 +17,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.Types
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
@@ -62,15 +60,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
@@ -155,7 +155,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def fix_actor(%{"attributedTo" => actor} = object) do
- Map.put(object, "actor", Containment.get_actor(%{"actor" => actor}))
+ actor = Containment.get_actor(%{"actor" => actor})
+
+ # TODO: Remove actor field for Objects
+ object
+ |> Map.put("actor", actor)
+ |> Map.put("attributedTo", actor)
end
def fix_in_reply_to(object, options \\ [])
@@ -163,7 +168,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
when not is_nil(in_reply_to) do
in_reply_to_id = prepare_in_reply_to(in_reply_to)
- object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
depth = (options[:depth] || 0) + 1
if Federator.allowed_thread_distance?(depth) do
@@ -171,12 +175,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%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"])
+ |> Map.drop(["conversation", "inReplyToAtomUri"])
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
@@ -207,7 +210,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
object
|> Map.put("context", context)
- |> Map.put("conversation", context)
+ |> Map.drop(["conversation"])
end
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
@@ -233,18 +236,28 @@ 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,
+ "type" => Map.get(url || %{}, "type", "Link")
+ }
+ |> Maps.put_if_present("mediaType", media_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],
+ "type" => data["type"] || "Document"
+ }
+ |> Maps.put_if_present("mediaType", media_type)
+ |> Maps.put_if_present("name", data["name"])
+ else
+ nil
+ end
end)
+ |> Enum.filter(& &1)
Map.put(object, "attachment", attachments)
end
@@ -261,14 +274,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "url", url["href"])
end
- 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)
+ def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
+ attachment =
+ Enum.find(url, fn x ->
+ media_type = x["mediaType"] || x["mimeType"] || ""
+
+ is_map(x) and String.starts_with?(media_type, "video/")
+ end)
- link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" 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
@@ -298,9 +316,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(mapping, name, data["icon"]["url"])
end)
- # we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats
- emoji = Map.merge(object["emoji"] || %{}, emoji)
-
Map.put(object, "emoji", emoji)
end
@@ -368,32 +383,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp fix_content(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),
- %Activity{} = activity <- Utils.fetch_latest_follow(follower, followed) do
- {:ok, activity}
- else
- _ -> {:error, nil}
- end
- end
-
- defp mastodon_follow_hack(_, _), do: {:error, nil}
-
- defp get_follow_activity(follow_object, followed) do
- with object_id when not is_nil(object_id) <- Utils.get_ap_id(follow_object),
- {_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(object_id)} do
- {:ok, activity}
- else
- # Can't find the activity. This might a Mastodon 2.3 "Accept"
- {:activity, nil} ->
- mastodon_follow_hack(follow_object, followed)
-
- _ ->
- {:error, nil}
- end
- end
-
# Reduce the object list to find the reported user.
defp get_reported(objects) do
Enum.reduce_while(objects, nil, fn ap_id, _ ->
@@ -405,6 +394,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end)
end
+ # Compatibility wrapper for Mastodon votes
+ defp handle_create(%{"object" => %{"type" => "Answer"}} = data, _user) do
+ handle_incoming(data)
+ end
+
+ defp handle_create(%{"object" => object} = data, user) do
+ %{
+ to: data["to"],
+ object: object,
+ actor: user,
+ context: object["context"],
+ local: false,
+ published: data["published"],
+ additional:
+ Map.take(data, [
+ "cc",
+ "directMessage",
+ "id"
+ ])
+ }
+ |> ActivityPub.create()
+ end
+
def handle_incoming(data, options \\ [])
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
@@ -443,33 +455,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
options
)
- when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
+ when objtype in ~w{Article Note Video Page} 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
- object = fix_object(object, options)
-
- params = %{
- to: data["to"],
- object: object,
- actor: user,
- context: object["conversation"],
- local: false,
- published: data["published"],
- additional:
- Map.take(data, [
- "cc",
- "directMessage",
- "id"
- ])
- }
+ {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor) do
+ data =
+ data
+ |> Map.put("object", fix_object(object, options))
+ |> Map.put("actor", actor)
+ |> fix_addressing()
- with {:ok, created_activity} <- ActivityPub.create(params) do
+ with {:ok, created_activity} <- handle_create(data, user) do
reply_depth = (options[:depth] || 0) + 1
if Federator.allowed_thread_distance?(reply_depth) do
@@ -520,120 +517,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
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
- 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_for_all(follow_activity, "accept"),
- %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
- {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
- User.update_follower_count(followed)
- User.update_following_count(follower)
-
- Notification.update_notification_type(followed, follow_activity)
-
- ActivityPub.accept(%{
- to: follow_activity.data["to"],
- type: "Accept",
- actor: followed,
- object: follow_activity.data["id"],
- local: false,
- activity_id: id
- })
- else
- _e ->
- :error
- end
- end
-
- def handle_incoming(
- %{"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_for_all(follow_activity, "reject"),
- %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
- {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
- {:ok, activity} <-
- ActivityPub.reject(%{
- to: follow_activity.data["to"],
- type: "Reject",
- actor: followed,
- object: follow_activity.data["id"],
- local: false,
- activity_id: id
- }) do
- {:ok, activity}
- else
- _e -> :error
- end
- end
-
@misskey_reactions %{
"like" => "👍",
"love" => "❤️",
@@ -663,9 +546,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Create", "object" => %{"type" => "ChatMessage"}} = data,
+ %{"type" => "Create", "object" => %{"type" => objtype}} = data,
_options
- ) do
+ )
+ when objtype in ~w{Question Answer ChatMessage Audio Event} do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
@@ -673,7 +557,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 +568,14 @@ 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 Accept Reject} do
+ with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
+ {:ok, activity, _} <-
+ Pipeline.common_pipeline(data, local: false) do
+ {:ok, activity}
end
end
@@ -720,12 +583,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Delete"} = data,
_options
) do
- with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
+ with {:ok, activity, _} <-
+ Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
{:error, {:validate_object, _}} = e ->
# Check if we have a create activity for this
- with {:ok, object_id} <- Types.ObjectID.cast(data["object"]),
+ with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
%Activity{data: %{"actor" => actor}} <-
Activity.create_by_object_ap_id(object_id) |> Repo.one(),
# We have one, insert a tombstone and retry
@@ -789,21 +653,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..5c349bb7a 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,15 +58,10 @@ 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,
- do: :local,
- else: :remote
+ cfg_key = if local, do: :local, else: :remote
- if Pleroma.Config.get([:restrict_unauthenticated, :activities, cfg_key]),
+ if Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key),
do: false,
else: is_public?(activity)
end