aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/config.ex18
-rw-r--r--lib/pleroma/config/transfer_task.ex2
-rw-r--r--lib/pleroma/keys.ex12
-rw-r--r--lib/pleroma/list.ex29
-rw-r--r--lib/pleroma/user.ex21
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex57
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex64
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex5
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex8
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex17
-rw-r--r--lib/pleroma/web/common_api/common_api.ex30
-rw-r--r--lib/pleroma/web/common_api/utils.ex17
-rw-r--r--lib/pleroma/web/salmon/salmon.ex25
13 files changed, 224 insertions, 81 deletions
diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex
index a71bcd447..a7d0fac5d 100644
--- a/lib/mix/tasks/pleroma/config.ex
+++ b/lib/mix/tasks/pleroma/config.ex
@@ -28,6 +28,14 @@ defmodule Mix.Tasks.Pleroma.Config do
|> Enum.reject(fn {k, _v} -> k in [Pleroma.Repo, :env] end)
|> Enum.each(fn {k, v} ->
key = to_string(k) |> String.replace("Elixir.", "")
+
+ key =
+ if String.starts_with?(key, "Pleroma.") do
+ key
+ else
+ ":" <> key
+ end
+
{:ok, _} = Config.update_or_create(%{group: "pleroma", key: key, value: v})
Mix.shell().info("#{key} is migrated.")
end)
@@ -53,17 +61,9 @@ defmodule Mix.Tasks.Pleroma.Config do
Repo.all(Config)
|> Enum.each(fn config ->
- mark =
- if String.starts_with?(config.key, "Pleroma.") or
- String.starts_with?(config.key, "Ueberauth"),
- do: ",",
- else: ":"
-
IO.write(
file,
- "config :#{config.group}, #{config.key}#{mark} #{
- inspect(Config.from_binary(config.value))
- }\r\n"
+ "config :#{config.group}, #{config.key}, #{inspect(Config.from_binary(config.value))}\r\n\r\n"
)
if delete? do
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
index 3c13a0558..7799b2a78 100644
--- a/lib/pleroma/config/transfer_task.ex
+++ b/lib/pleroma/config/transfer_task.ex
@@ -35,7 +35,7 @@ defmodule Pleroma.Config.TransferTask do
if String.starts_with?(setting.key, "Pleroma.") do
"Elixir." <> setting.key
else
- setting.key
+ String.trim_leading(setting.key, ":")
end
group = String.to_existing_atom(setting.group)
diff --git a/lib/pleroma/keys.ex b/lib/pleroma/keys.ex
index b7bc7a4da..6dd31d3bd 100644
--- a/lib/pleroma/keys.ex
+++ b/lib/pleroma/keys.ex
@@ -35,10 +35,12 @@ defmodule Pleroma.Keys do
end
def keys_from_pem(pem) do
- [private_key_code] = :public_key.pem_decode(pem)
- private_key = :public_key.pem_entry_decode(private_key_code)
- {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key
- public_key = {:RSAPublicKey, modulus, exponent}
- {:ok, private_key, public_key}
+ with [private_key_code] <- :public_key.pem_decode(pem),
+ private_key <- :public_key.pem_entry_decode(private_key_code),
+ {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} <- private_key do
+ {:ok, private_key, {:RSAPublicKey, modulus, exponent}}
+ else
+ error -> {:error, error}
+ end
end
end
diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex
index a5b1cad68..1d320206e 100644
--- a/lib/pleroma/list.ex
+++ b/lib/pleroma/list.ex
@@ -16,6 +16,7 @@ defmodule Pleroma.List do
belongs_to(:user, User, type: Pleroma.FlakeId)
field(:title, :string)
field(:following, {:array, :string}, default: [])
+ field(:ap_id, :string)
timestamps()
end
@@ -55,6 +56,10 @@ defmodule Pleroma.List do
Repo.one(query)
end
+ def get_by_ap_id(ap_id) do
+ Repo.get_by(__MODULE__, ap_id: ap_id)
+ end
+
def get_following(%Pleroma.List{following: following} = _list) do
q =
from(
@@ -105,7 +110,14 @@ defmodule Pleroma.List do
def create(title, %User{} = creator) do
list = %Pleroma.List{user_id: creator.id, title: title}
- Repo.insert(list)
+
+ Repo.transaction(fn ->
+ list = Repo.insert!(list)
+
+ list
+ |> change(ap_id: "#{creator.ap_id}/lists/#{list.id}")
+ |> Repo.update!()
+ end)
end
def follow(%Pleroma.List{following: following} = list, %User{} = followed) do
@@ -125,4 +137,19 @@ defmodule Pleroma.List do
|> follow_changeset(attrs)
|> Repo.update()
end
+
+ def memberships(%User{follower_address: follower_address}) do
+ Pleroma.List
+ |> where([l], ^follower_address in l.following)
+ |> select([l], l.ap_id)
+ |> Repo.all()
+ end
+
+ def memberships(_), do: []
+
+ def member?(%Pleroma.List{following: following}, %User{follower_address: follower_address}) do
+ Enum.member?(following, follower_address)
+ end
+
+ def member?(_, _), do: false
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 29c87d4a9..ffba3f390 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1190,10 +1190,12 @@ defmodule Pleroma.User do
end
# OStatus Magic Key
- def public_key_from_info(%{magic_key: magic_key}) do
+ def public_key_from_info(%{magic_key: magic_key}) when not is_nil(magic_key) do
{:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
end
+ def public_key_from_info(_), do: {:error, "not found key"}
+
def get_public_key_for_ap_id(ap_id) do
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
{:ok, public_key} <- public_key_from_info(user.info) do
@@ -1379,23 +1381,16 @@ defmodule Pleroma.User do
}
end
- def ensure_keys_present(user) do
- info = user.info
-
+ def ensure_keys_present(%User{info: info} = user) do
if info.keys do
{:ok, user}
else
{:ok, pem} = Keys.generate_rsa_pem()
- info_cng =
- info
- |> User.Info.set_keys(pem)
-
- cng =
- Ecto.Changeset.change(user)
- |> Ecto.Changeset.put_embed(:info, info_cng)
-
- update_and_set_cache(cng)
+ user
+ |> Ecto.Changeset.change()
+ |> Ecto.Changeset.put_embed(:info, User.Info.set_keys(info, pem))
+ |> update_and_set_cache()
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 87963b691..31397b09f 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -27,19 +27,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
# For Announce activities, we filter the recipients based on following status for any actors
# that match actual users. See issue #164 for more information about why this is necessary.
defp get_recipients(%{"type" => "Announce"} = data) do
- to = data["to"] || []
- cc = data["cc"] || []
+ to = Map.get(data, "to", [])
+ cc = Map.get(data, "cc", [])
+ bcc = Map.get(data, "bcc", [])
actor = User.get_cached_by_ap_id(data["actor"])
recipients =
- (to ++ cc)
- |> Enum.filter(fn recipient ->
+ Enum.filter(Enum.concat([to, cc, bcc]), fn recipient ->
case User.get_cached_by_ap_id(recipient) do
- nil ->
- true
-
- user ->
- User.following?(user, actor)
+ nil -> true
+ user -> User.following?(user, actor)
end
end)
@@ -47,17 +44,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
defp get_recipients(%{"type" => "Create"} = data) do
- to = data["to"] || []
- cc = data["cc"] || []
- actor = data["actor"] || []
- recipients = (to ++ cc ++ [actor]) |> Enum.uniq()
+ to = Map.get(data, "to", [])
+ cc = Map.get(data, "cc", [])
+ bcc = Map.get(data, "bcc", [])
+ actor = Map.get(data, "actor", [])
+ recipients = [to, cc, bcc, [actor]] |> Enum.concat() |> Enum.uniq()
{recipients, to, cc}
end
defp get_recipients(data) do
- to = data["to"] || []
- cc = data["cc"] || []
- recipients = to ++ cc
+ to = Map.get(data, "to", [])
+ cc = Map.get(data, "cc", [])
+ bcc = Map.get(data, "bcc", [])
+ recipients = Enum.concat([to, cc, bcc])
{recipients, to, cc}
end
@@ -898,13 +897,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp maybe_order(query, _), do: query
def fetch_activities_query(recipients, opts \\ %{}) do
- base_query = from(activity in Activity)
-
config = %{
skip_thread_containment: Config.get([:instance, :skip_thread_containment])
}
- base_query
+ Activity
|> maybe_preload_objects(opts)
|> maybe_preload_bookmarks(opts)
|> maybe_set_thread_muted_field(opts)
@@ -933,11 +930,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def fetch_activities(recipients, opts \\ %{}) do
- fetch_activities_query(recipients, opts)
+ list_memberships = Pleroma.List.memberships(opts["user"])
+
+ fetch_activities_query(recipients ++ list_memberships, opts)
|> Pagination.fetch_paginated(opts)
|> Enum.reverse()
+ |> maybe_update_cc(list_memberships, opts["user"])
end
+ defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id})
+ when is_list(list_memberships) and length(list_memberships) > 0 do
+ Enum.map(activities, fn
+ %{data: %{"bcc" => bcc}} = activity when is_list(bcc) and length(bcc) > 0 ->
+ if Enum.any?(bcc, &(&1 in list_memberships)) do
+ update_in(activity.data["cc"], &[user_ap_id | &1])
+ else
+ activity
+ end
+
+ activity ->
+ activity
+ end)
+ end
+
+ defp maybe_update_cc(activities, _, _), do: activities
+
def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
from(activity in query,
where:
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index a05e03263..18145e45f 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -92,18 +92,68 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
end
end
- @doc """
- Publishes an activity to all relevant peers.
- """
- def publish(%User{} = actor, %Activity{} = activity) do
- remote_followers =
+ defp recipients(actor, activity) do
+ followers =
if actor.follower_address in activity.recipients do
{:ok, followers} = User.get_followers(actor)
- followers |> Enum.filter(&(!&1.local))
+ Enum.filter(followers, &(!&1.local))
else
[]
end
+ Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers
+ end
+
+ defp get_cc_ap_ids(ap_id, recipients) do
+ host = Map.get(URI.parse(ap_id), :host)
+
+ recipients
+ |> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end)
+ |> Enum.map(& &1.ap_id)
+ end
+
+ @doc """
+ Publishes an activity with BCC to all relevant peers.
+ """
+
+ def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do
+ public = is_public?(activity)
+ {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
+
+ recipients = recipients(actor, activity)
+
+ recipients
+ |> Enum.filter(&User.ap_enabled?/1)
+ |> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end)
+ |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
+ |> Instances.filter_reachable()
+ |> Enum.each(fn {inbox, unreachable_since} ->
+ %User{ap_id: ap_id} =
+ Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end)
+
+ # Get all the recipients on the same host and add them to cc. Otherwise it a remote
+ # instance would only accept a first message for the first recipient and ignore the rest.
+ cc = get_cc_ap_ids(ap_id, recipients)
+
+ json =
+ data
+ |> Map.put("cc", cc)
+ |> Jason.encode!()
+
+ Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{
+ inbox: inbox,
+ json: json,
+ actor: actor,
+ id: activity.data["id"],
+ unreachable_since: unreachable_since
+ })
+ end)
+ end
+
+ @doc """
+ Publishes an activity to all relevant peers.
+ """
+ def publish(%User{} = actor, %Activity{} = activity) do
public = is_public?(activity)
if public && Config.get([:instance, :allow_relay]) do
@@ -114,7 +164,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
json = Jason.encode!(data)
- (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
+ recipients(actor, activity)
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|> Enum.map(fn %{info: %{source_data: data}} ->
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index d14490bb5..602ae48e1 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -814,13 +814,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do
object =
- Object.normalize(object_id).data
+ object_id
+ |> Object.normalize()
+ |> Map.get(:data)
|> prepare_object
data =
data
|> Map.put("object", object)
|> Map.merge(Utils.make_json_ld_header())
+ |> Map.delete("bcc")
{:ok, data}
end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 4288ea4c8..c146f59d4 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -25,12 +25,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
# Some implementations send the actor URI as the actor field, others send the entire actor object,
# so figure out what the actor's URI is based on what we have.
- def get_ap_id(object) do
- case object do
- %{"id" => id} -> id
- id -> id
- end
- end
+ def get_ap_id(%{"id" => id} = _), do: id
+ def get_ap_id(id), do: id
def normalize_params(params) do
Map.put(params, "actor", get_ap_id(params["actor"]))
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 9908a2e75..2666edc7c 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -34,6 +34,20 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
!is_public?(activity) && !is_private?(activity)
end
+ def is_list?(%{data: %{"listMessage" => _}}), do: true
+ def is_list?(_), do: false
+
+ def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
+
+ def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
+ user.ap_id in activity.data["to"] ||
+ list_ap_id
+ |> Pleroma.List.get_by_ap_id()
+ |> Pleroma.List.member?(user)
+ end
+
+ def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
+
def visible_for_user?(activity, nil) do
is_public?(activity)
end
@@ -73,6 +87,9 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
object.data["directMessage"] == true ->
"direct"
+ is_binary(object.data["listMessage"]) ->
+ "list"
+
length(cc) > 0 ->
"private"
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 949baa3b0..44669b228 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -176,6 +176,11 @@ defmodule Pleroma.Web.CommonAPI do
when visibility in ~w{public unlisted private direct},
do: {visibility, get_replied_to_visibility(in_reply_to)}
+ def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to) do
+ visibility = {:list, String.to_integer(list_id)}
+ {visibility, get_replied_to_visibility(in_reply_to)}
+ end
+
def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do
visibility = get_replied_to_visibility(in_reply_to)
{visibility, visibility}
@@ -236,19 +241,18 @@ defmodule Pleroma.Web.CommonAPI do
"emoji",
Map.merge(Formatter.get_emoji_map(full_payload), poll_emoji)
) do
- res =
- ActivityPub.create(
- %{
- to: to,
- actor: user,
- context: context,
- object: object,
- additional: %{"cc" => cc, "directMessage" => visibility == "direct"}
- },
- Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
- )
-
- res
+ preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
+ direct? = visibility == "direct"
+
+ %{
+ to: to,
+ actor: user,
+ context: context,
+ object: object,
+ additional: %{"cc" => cc, "directMessage" => direct?}
+ }
+ |> maybe_add_list_data(user, visibility)
+ |> ActivityPub.create(preview?)
else
{:private_to_public, true} ->
{:error, dgettext("errors", "The message visibility must be direct")}
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 8e482eef7..f28a96320 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -100,12 +100,29 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
+ def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}), do: {mentions, []}
+
def get_addressed_users(_, to) when is_list(to) do
User.get_ap_ids_by_nicknames(to)
end
def get_addressed_users(mentioned_users, _), do: mentioned_users
+ def maybe_add_list_data(activity_params, user, {:list, list_id}) do
+ case Pleroma.List.get(list_id, user) do
+ %Pleroma.List{} = list ->
+ activity_params
+ |> put_in([:additional, "bcc"], [list.ap_id])
+ |> put_in([:additional, "listMessage"], list.ap_id)
+ |> put_in([:object, "listMessage"], list.ap_id)
+
+ _ ->
+ activity_params
+ end
+ end
+
+ def maybe_add_list_data(activity_params, _, _), do: activity_params
+
def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data)
when is_list(options) do
%{max_expiration: max_expiration, min_expiration: min_expiration} =
diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex
index e96e4e1e4..9b01ebcc6 100644
--- a/lib/pleroma/web/salmon/salmon.ex
+++ b/lib/pleroma/web/salmon/salmon.ex
@@ -123,11 +123,26 @@ defmodule Pleroma.Web.Salmon do
{:ok, salmon}
end
- def remote_users(%{data: %{"to" => to} = data}) do
- to = to ++ (data["cc"] || [])
+ def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do
+ cc = Map.get(data, "cc", [])
+
+ bcc =
+ data
+ |> Map.get("bcc", [])
+ |> Enum.reduce([], fn ap_id, bcc ->
+ case Pleroma.List.get_by_ap_id(ap_id) do
+ %Pleroma.List{user_id: ^user_id} = list ->
+ {:ok, following} = Pleroma.List.get_following(list)
+ bcc ++ Enum.map(following, & &1.ap_id)
+
+ _ ->
+ bcc
+ end
+ end)
- to
- |> Enum.map(fn id -> User.get_cached_by_ap_id(id) end)
+ [to, cc, bcc]
+ |> Enum.concat()
+ |> Enum.map(&User.get_cached_by_ap_id/1)
|> Enum.filter(fn user -> user && !user.local end)
end
@@ -191,7 +206,7 @@ defmodule Pleroma.Web.Salmon do
{:ok, private, _} = Keys.keys_from_pem(keys)
{:ok, feed} = encode(private, feed)
- remote_users = remote_users(activity)
+ remote_users = remote_users(user, activity)
salmon_urls = Enum.map(remote_users, & &1.info.salmon)
reachable_urls_metadata = Instances.filter_reachable(salmon_urls)