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.ex59
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex12
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex109
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex20
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex14
5 files changed, 168 insertions, 46 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index a4b49e73c..298d7817a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -48,7 +48,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def insert(map, local \\ true) when is_map(map) do
- with nil <- Activity.get_by_ap_id(map["id"]),
+ with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map),
:ok <- check_actor_is_active(map["actor"]),
{:ok, map} <- MRF.filter(map),
@@ -83,6 +83,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
if activity.local do
Pleroma.Web.Streamer.stream("public:local", activity)
end
+
+ if activity.data["object"]["attachment"] != [] do
+ Pleroma.Web.Streamer.stream("public:media", activity)
+
+ if activity.local do
+ Pleroma.Web.Streamer.stream("public:local:media", activity)
+ end
+ end
else
if !Enum.member?(activity.data["cc"] || [], public) &&
!Enum.member?(
@@ -261,16 +269,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
- follow_activity = fetch_latest_follow(blocker, blocked)
+ ap_config = Application.get_env(:pleroma, :activitypub)
+ unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked)
+ outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks)
- if follow_activity do
- unfollow(blocker, blocked, nil, local)
+ with true <- unfollow_blocked do
+ follow_activity = fetch_latest_follow(blocker, blocked)
+
+ if follow_activity do
+ unfollow(blocker, blocked, nil, local)
+ end
end
- with block_data <- make_block_data(blocker, blocked, activity_id),
+ with true <- outgoing_blocks,
+ block_data <- make_block_data(blocker, blocked, activity_id),
{:ok, activity} <- insert(block_data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
+ else
+ _e -> {:ok, nil}
end
end
@@ -448,6 +465,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_media(query, _), do: query
+ defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
+ from(
+ activity in query,
+ where: fragment("?->'object'->>'inReplyTo' is null", activity.data)
+ )
+ end
+
+ defp restrict_replies(query, _), do: query
+
# Only search through last 100_000 activities by default
defp restrict_recent(query, %{"whole_db" => true}), do: query
@@ -505,6 +531,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_blocked(opts)
|> restrict_media(opts)
|> restrict_visibility(opts)
+ |> restrict_replies(opts)
end
def fetch_activities(recipients, opts \\ %{}) do
@@ -556,7 +583,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def fetch_and_prepare_user_from_ap_id(ap_id) do
with {:ok, %{status_code: 200, body: body}} <-
- @httpoison.get(ap_id, Accept: "application/activity+json"),
+ @httpoison.get(ap_id, [Accept: "application/activity+json"], follow_redirect: true),
{:ok, data} <- Jason.decode(body) do
user_data_from_user_object(data)
else
@@ -632,13 +659,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Logger.info("Federating #{id} to #{inbox}")
host = URI.parse(inbox).host
+ digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
+
signature =
- Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)})
+ Pleroma.Web.HTTPSignatures.sign(actor, %{
+ host: host,
+ "content-length": byte_size(json),
+ digest: digest
+ })
@httpoison.post(
inbox,
json,
- [{"Content-Type", "application/activity+json"}, {"signature", signature}],
+ [
+ {"Content-Type", "application/activity+json"},
+ {"signature", signature},
+ {"digest", digest}
+ ],
hackney: [pool: :default]
)
end
@@ -661,7 +698,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
recv_timeout: 20000
),
{:ok, data} <- Jason.decode(body),
- nil <- Object.get_by_ap_id(data["id"]),
+ nil <- Object.normalize(data),
params <- %{
"type" => "Create",
"to" => data["to"],
@@ -670,7 +707,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"object" => data
},
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
- {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
+ {:ok, Object.normalize(activity.data["object"])}
else
object = %Object{} ->
{:ok, object}
@@ -679,7 +716,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
case OStatus.fetch_activity_from_url(id) do
- {:ok, [activity | _]} -> {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
+ {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
e -> e
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 8d770387d..7fecb8a4f 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -4,6 +4,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
@mrf_policy Application.get_env(:pleroma, :mrf_simple)
+ @accept Keyword.get(@mrf_policy, :accept)
+ defp check_accept(actor_info, object) do
+ if length(@accept) > 0 and not (actor_info.host in @accept) do
+ {:reject, nil}
+ else
+ {:ok, object}
+ end
+ end
+
@reject Keyword.get(@mrf_policy, :reject)
defp check_reject(actor_info, object) do
if actor_info.host in @reject do
@@ -74,7 +83,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
def filter(object) do
actor_info = URI.parse(object["actor"])
- with {:ok, object} <- check_reject(actor_info, object),
+ with {:ok, object} <- check_accept(actor_info, object),
+ {:ok, object} <- check_reject(actor_info, object),
{:ok, object} <- check_media_removal(actor_info, object),
{:ok, object} <- check_media_nsfw(actor_info, object),
{:ok, object} <- check_ftl_removal(actor_info, object) do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 300e0fcdd..1367bc7e3 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -13,17 +13,73 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
require Logger
+ def get_actor(%{"actor" => actor}) when is_binary(actor) do
+ actor
+ end
+
+ def get_actor(%{"actor" => actor}) when is_list(actor) do
+ if is_binary(Enum.at(actor, 0)) do
+ Enum.at(actor, 0)
+ else
+ Enum.find(actor, fn %{"type" => type} -> type == "Person" end)
+ |> Map.get("id")
+ end
+ end
+
+ def get_actor(%{"actor" => actor}) when is_map(actor) do
+ actor["id"]
+ end
+
@doc """
Modifies an incoming AP object (mastodon format) to our internal format.
"""
def fix_object(object) do
object
- |> Map.put("actor", object["attributedTo"])
+ |> fix_actor
|> fix_attachments
|> fix_context
|> fix_in_reply_to
|> fix_emoji
|> fix_tag
+ |> fix_content_map
+ |> fix_likes
+ |> fix_addressing
+ end
+
+ def fix_addressing_list(map, field) do
+ if is_binary(map[field]) do
+ map
+ |> Map.put(field, [map[field]])
+ else
+ map
+ end
+ end
+
+ def fix_addressing(map) do
+ map
+ |> fix_addressing_list("to")
+ |> fix_addressing_list("cc")
+ |> fix_addressing_list("bto")
+ |> fix_addressing_list("bcc")
+ end
+
+ def fix_actor(%{"attributedTo" => actor} = object) do
+ object
+ |> Map.put("actor", get_actor(%{"actor" => actor}))
+ end
+
+ def fix_likes(%{"likes" => likes} = object)
+ when is_bitstring(likes) do
+ # Check for standardisation
+ # This is what Peertube does
+ # curl -H 'Accept: application/activity+json' $likes | jq .totalItems
+ object
+ |> Map.put("likes", [])
+ |> Map.put("like_count", 0)
+ end
+
+ def fix_likes(object) do
+ object
end
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
@@ -53,8 +109,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_in_reply_to(object), do: object
def fix_context(object) do
+ context = object["context"] || object["conversation"] || Utils.generate_context_id()
+
object
- |> Map.put("context", object["conversation"])
+ |> Map.put("context", context)
+ |> Map.put("conversation", context)
end
def fix_attachments(object) do
@@ -107,10 +166,34 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("tag", combined)
end
+ # content map usually only has one language so this will do for now.
+ def fix_content_map(%{"contentMap" => content_map} = object) do
+ content_groups = Map.to_list(content_map)
+ {_, content} = Enum.at(content_groups, 0)
+
+ object
+ |> Map.put("content", content)
+ end
+
+ def fix_content_map(object), do: object
+
+ # disallow objects with bogus IDs
+ def handle_incoming(%{"id" => nil}), do: :error
+ def handle_incoming(%{"id" => ""}), do: :error
+ # length of https:// = 8, should validate better, but good enough for now.
+ def handle_incoming(%{"id" => id}) when not (is_binary(id) and length(id) > 8), do: :error
+
# TODO: validate those with a Ecto scheme
# - tags
# - emoji
- def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
+ def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
+ when objtype in ["Article", "Note", "Video"] do
+ actor = get_actor(data)
+
+ data =
+ Map.put(data, "actor", actor)
+ |> fix_addressing
+
with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
object = fix_object(data["object"])
@@ -400,7 +483,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming(_), do: :error
def get_obj_helper(id) do
- if object = Object.get_by_ap_id(id), do: {:ok, object}, else: nil
+ if object = Object.normalize(id), do: {:ok, object}, else: nil
end
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do
@@ -448,14 +531,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
# because of course it does.
def prepare_outgoing(%{"type" => "Accept"} = data) do
- follow_activity_id =
- if is_binary(data["object"]) do
- data["object"]
- else
- data["object"]["id"]
- end
-
- with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
+ with follow_activity <- Activity.normalize(data["object"]) do
object = %{
"actor" => follow_activity.actor,
"object" => follow_activity.data["object"],
@@ -473,14 +549,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def prepare_outgoing(%{"type" => "Reject"} = data) do
- follow_activity_id =
- if is_binary(data["object"]) do
- data["object"]
- else
- data["object"]["id"]
- end
-
- with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
+ with follow_activity <- Activity.normalize(data["object"]) do
object = %{
"actor" => follow_activity.actor,
"object" => follow_activity.data["object"],
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 64329b710..7cdc1656b 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -128,7 +128,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Inserts a full object if it is contained in an activity.
"""
def insert_full_object(%{"object" => %{"type" => type} = object_data})
- when is_map(object_data) and type in ["Note"] do
+ when is_map(object_data) and type in ["Article", "Note", "Video"] do
with {:ok, _} <- Object.create(object_data) do
:ok
end
@@ -204,13 +204,17 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do
- with likes <- [actor | object.data["likes"] || []] |> Enum.uniq() do
+ likes = if is_list(object.data["likes"]), do: object.data["likes"], else: []
+
+ with likes <- [actor | likes] |> Enum.uniq() do
update_likes_in_object(likes, object)
end
end
def remove_like_from_object(%Activity{data: %{"actor" => actor}}, object) do
- with likes <- (object.data["likes"] || []) |> List.delete(actor) do
+ likes = if is_list(object.data["likes"]), do: object.data["likes"], else: []
+
+ with likes <- likes |> List.delete(actor) do
update_likes_in_object(likes, object)
end
end
@@ -357,13 +361,19 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do
- with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do
+ announcements =
+ if is_list(object.data["announcements"]), do: object.data["announcements"], else: []
+
+ with announcements <- [actor | announcements] |> Enum.uniq() do
update_element_in_object("announcement", announcements, object)
end
end
def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do
- with announcements <- (object.data["announcements"] || []) |> List.delete(actor) do
+ announcements =
+ if is_list(object.data["announcements"]), do: object.data["announcements"], else: []
+
+ with announcements <- announcements |> List.delete(actor) do
update_element_in_object("announcement", announcements, object)
end
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index f4b2e0610..fc76f2940 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("user.json", %{user: user}) do
{:ok, user} = WebFinger.ensure_keys_present(user)
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
- public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
+ public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
%{
@@ -42,7 +42,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"image" => %{
"type" => "Image",
"url" => User.banner_url(user)
- }
+ },
+ "tag" => user.info["source_data"]["tag"] || []
}
|> Map.merge(Utils.make_json_ld_header())
end
@@ -98,9 +99,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
info = User.user_info(user)
params = %{
- "type" => ["Create", "Announce"],
- "actor_id" => user.ap_id,
- "whole_db" => true,
"limit" => "10"
}
@@ -111,10 +109,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
params
end
- activities = ActivityPub.fetch_public_activities(params)
- min_id = Enum.at(activities, 0).id
-
- activities = Enum.reverse(activities)
+ activities = ActivityPub.fetch_user_activities(user, nil, params)
+ min_id = Enum.at(Enum.reverse(activities), 0).id
max_id = Enum.at(activities, 0).id
collection =