diff --git a/CHANGELOG.md b/CHANGELOG.md
index bcdd0615f..4043b1edf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,11 +23,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
- Existing user id not being preserved on insert conflict
- Rich Media: Parser failing when no TTL can be found by image TTL setters
+- ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification.
### Added
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
- MRF: Support for excluding specific domains from Transparency.
- MRF: Support for filtering posts based on who they mention (`Pleroma.Web.ActivityPub.MRF.MentionPolicy`)
+- MRF (Simple Policy): Support for wildcard domains.
+- Support for wildcard domains in user domain blocks setting.
+- Configuration: `quarantined_instances` support wildcard domains.
- Configuration: `federation_incoming_replies_max_depth` option
- Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses)
- Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header
diff --git a/docs/api/admin_api.md b/docs/api/admin_api.md
index c429da822..5ac3535c4 100644
--- a/docs/api/admin_api.md
+++ b/docs/api/admin_api.md
@@ -564,6 +564,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
## `/api/pleroma/admin/config`
### List config settings
+List config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.
- Method `GET`
- Params: none
- Response:
@@ -582,6 +583,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
## `/api/pleroma/admin/config`
### Update config settings
+Updating config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.
Module name can be passed as string, which starts with `Pleroma`, e.g. `"Pleroma.Upload"`.
Atom keys and values can be passed with `:` in the beginning, e.g. `":upload"`.
Tuples can be passed as `{"tuple": ["first_val", Pleroma.Module, []]}`.
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
index 2a0823ecf..0bf49fd7c 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -10,9 +10,18 @@ defmodule Pleroma.Signature do
alias Pleroma.Web.ActivityPub.ActivityPub
def key_id_to_actor_id(key_id) do
- URI.parse(key_id)
- |> Map.put(:fragment, nil)
- |> URI.to_string()
+ uri =
+ URI.parse(key_id)
+ |> Map.put(:fragment, nil)
+
+ uri =
+ if String.ends_with?(uri.path, "/publickey") do
+ Map.put(uri, :path, String.replace(uri.path, "/publickey", ""))
+ else
+ uri
+ end
+
+ URI.to_string(uri)
end
def fetch_public_key(conn) do
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 5ea2b518b..982ca8bc1 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -586,12 +586,23 @@ def get_followers_query(user, page) do
@spec get_followers_query(User.t()) :: Ecto.Query.t()
def get_followers_query(user), do: get_followers_query(user, nil)
+ @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
def get_followers(user, page \\ nil) do
q = get_followers_query(user, page)
{:ok, Repo.all(q)}
end
+ @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
+ def get_external_followers(user, page \\ nil) do
+ q =
+ user
+ |> get_followers_query(page)
+ |> User.Query.build(%{external: true})
+
+ {:ok, Repo.all(q)}
+ end
+
def get_followers_ids(user, page \\ nil) do
q = get_followers_query(user, page)
@@ -873,12 +884,17 @@ def muted_notifications?(user, %{ap_id: ap_id}),
def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
blocks = info.blocks
- domain_blocks = info.domain_blocks
+
+ domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(info.domain_blocks)
+
%{host: host} = URI.parse(ap_id)
- Enum.member?(blocks, ap_id) || Enum.any?(domain_blocks, &(&1 == host))
+ Enum.member?(blocks, ap_id) ||
+ Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
end
+ def blocks?(nil, _), do: false
+
def subscribed_to?(user, %{ap_id: ap_id}) do
with %User{} = target <- get_cached_by_ap_id(ap_id) do
Enum.member?(target.info.subscribers, user.ap_id)
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index 10ceef715..dd204b21c 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -25,4 +25,14 @@ def get_policies do
defp get_policies(policy) when is_atom(policy), do: [policy]
defp get_policies(policies) when is_list(policies), do: policies
defp get_policies(_), do: []
+
+ @spec subdomains_regex([String.t()]) :: [Regex.t()]
+ def subdomains_regex(domains) when is_list(domains) do
+ for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)
+ end
+
+ @spec subdomain_match?([Regex.t()], String.t()) :: boolean()
+ def subdomain_match?(domains, host) do
+ Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
+ 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 433d23c5f..2cf63d3db 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -4,22 +4,29 @@
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
+ @behaviour MRF
defp check_accept(%{host: actor_host} = _actor_info, object) do
- accepts = Pleroma.Config.get([:mrf_simple, :accept])
+ accepts =
+ Pleroma.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}
- Enum.member?(accepts, actor_host) -> {:ok, object}
+ MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
true -> {:reject, nil}
end
end
defp check_reject(%{host: actor_host} = _actor_info, object) do
- if Enum.member?(Pleroma.Config.get([:mrf_simple, :reject]), actor_host) do
+ rejects =
+ Pleroma.Config.get([:mrf_simple, :reject])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(rejects, actor_host) do
{:reject, nil}
else
{:ok, object}
@@ -31,8 +38,12 @@ defp check_media_removal(
%{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object
)
when length(child_attachment) > 0 do
+ media_removal =
+ Pleroma.Config.get([:mrf_simple, :media_removal])
+ |> MRF.subdomains_regex()
+
object =
- if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_removal]), actor_host) do
+ if MRF.subdomain_match?(media_removal, actor_host) do
child_object = Map.delete(object["object"], "attachment")
Map.put(object, "object", child_object)
else
@@ -51,8 +62,12 @@ defp check_media_nsfw(
"object" => child_object
} = object
) do
+ media_nsfw =
+ Pleroma.Config.get([:mrf_simple, :media_nsfw])
+ |> MRF.subdomains_regex()
+
object =
- if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do
+ if MRF.subdomain_match?(media_nsfw, actor_host) do
tags = (child_object["tag"] || []) ++ ["nsfw"]
child_object = Map.put(child_object, "tag", tags)
child_object = Map.put(child_object, "sensitive", true)
@@ -67,12 +82,12 @@ defp check_media_nsfw(
defp check_media_nsfw(_actor_info, object), do: {:ok, object}
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
+ timeline_removal =
+ Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
+ |> MRF.subdomains_regex()
+
object =
- with true <-
- Enum.member?(
- Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]),
- actor_host
- ),
+ with true <- MRF.subdomain_match?(timeline_removal, actor_host),
user <- User.get_cached_by_ap_id(object["actor"]),
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do
to =
@@ -94,7 +109,11 @@ defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
end
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
- if actor_host in Pleroma.Config.get([:mrf_simple, :report_removal]) do
+ report_removal =
+ Pleroma.Config.get([:mrf_simple, :report_removal])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(report_removal, actor_host) do
{:reject, nil}
else
{:ok, object}
@@ -104,7 +123,11 @@ defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"}
defp check_report_removal(_actor_info, object), do: {:ok, object}
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
- if actor_host in Pleroma.Config.get([:mrf_simple, :avatar_removal]) do
+ avatar_removal =
+ Pleroma.Config.get([:mrf_simple, :avatar_removal])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(avatar_removal, actor_host) do
{:ok, Map.delete(object, "icon")}
else
{:ok, object}
@@ -114,7 +137,11 @@ defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon}
defp check_avatar_removal(_actor_info, object), do: {:ok, object}
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
- if actor_host in Pleroma.Config.get([:mrf_simple, :banner_removal]) do
+ banner_removal =
+ Pleroma.Config.get([:mrf_simple, :banner_removal])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(banner_removal, actor_host) do
{:ok, Map.delete(object, "image")}
else
{:ok, object}
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index c505223f7..016d78216 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -87,18 +87,23 @@ defp should_federate?(inbox, public) do
if public do
true
else
- inbox_info = URI.parse(inbox)
- !Enum.member?(Config.get([:instance, :quarantined_instances], []), inbox_info.host)
+ %{host: host} = URI.parse(inbox)
+
+ quarantined_instances =
+ Config.get([:instance, :quarantined_instances], [])
+ |> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
+
+ !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
end
end
+ @spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
defp recipients(actor, activity) do
- followers =
+ {:ok, followers} =
if actor.follower_address in activity.recipients do
- {:ok, followers} = User.get_followers(actor)
- Enum.filter(followers, &(!&1.local))
+ User.get_external_followers(actor)
else
- []
+ {:ok, []}
end
Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers
@@ -112,6 +117,45 @@ defp get_cc_ap_ids(ap_id, recipients) do
|> Enum.map(& &1.ap_id)
end
+ @as_public "https://www.w3.org/ns/activitystreams#Public"
+
+ defp maybe_use_sharedinbox(%User{info: %{source_data: data}}),
+ do: (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
+
+ @doc """
+ Determine a user inbox to use based on heuristics. These heuristics
+ are based on an approximation of the ``sharedInbox`` rules in the
+ [ActivityPub specification][ap-sharedinbox].
+
+ Please do not edit this function (or its children) without reading
+ the spec, as editing the code is likely to introduce some breakage
+ without some familiarity.
+
+ [ap-sharedinbox]: https://www.w3.org/TR/activitypub/#shared-inbox-delivery
+ """
+ def determine_inbox(
+ %Activity{data: activity_data},
+ %User{info: %{source_data: data}} = user
+ ) do
+ to = activity_data["to"] || []
+ cc = activity_data["cc"] || []
+ type = activity_data["type"]
+
+ cond do
+ type == "Delete" ->
+ maybe_use_sharedinbox(user)
+
+ @as_public in to || @as_public in cc ->
+ maybe_use_sharedinbox(user)
+
+ length(to) + length(cc) > 1 ->
+ maybe_use_sharedinbox(user)
+
+ true ->
+ data["inbox"]
+ end
+ end
+
@doc """
Publishes an activity with BCC to all relevant peers.
"""
@@ -166,8 +210,8 @@ def publish(%User{} = actor, %Activity{} = activity) do
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"]
+ |> Enum.map(fn %User{} = user ->
+ determine_inbox(activity, user)
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 2666edc7c..097fceb08 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -8,14 +8,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Repo
alias Pleroma.User
+ @public "https://www.w3.org/ns/activitystreams#Public"
+
+ @spec is_public?(Object.t() | Activity.t() | map()) :: boolean()
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false
-
- def is_public?(data) do
- "https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
- end
+ def is_public?(data), do: @public in (data["to"] ++ (data["cc"] || []))
def is_private?(activity) do
with false <- is_public?(activity),
@@ -69,15 +69,14 @@ def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
end
def get_visibility(object) do
- public = "https://www.w3.org/ns/activitystreams#Public"
to = object.data["to"] || []
cc = object.data["cc"] || []
cond do
- public in to ->
+ @public in to ->
"public"
- public in cc ->
+ @public in cc ->
"unlisted"
# this should use the sql for the object's activity
diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex
index b4eb8e002..dde05ea7b 100644
--- a/lib/pleroma/web/admin_api/config.ex
+++ b/lib/pleroma/web/admin_api/config.ex
@@ -84,6 +84,7 @@ defp do_convert(entity) when is_map(entity) do
end
defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]}
+ defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]}
defp do_convert(entity) when is_tuple(entity),
do: %{"tuple" => do_convert(Tuple.to_list(entity))}
@@ -113,11 +114,15 @@ def transform(entity), do: :erlang.term_to_binary(entity)
defp do_transform(%Regex{} = entity) when is_map(entity), do: entity
defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do
- cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
- {dispatch_settings, []} = Code.eval_string(cleaned_string, [], requires: [], macros: [])
+ {dispatch_settings, []} = do_eval(entity)
{:dispatch, [dispatch_settings]}
end
+ defp do_transform(%{"tuple" => [":partial_chain", entity]}) do
+ {partial_chain, []} = do_eval(entity)
+ {:partial_chain, partial_chain}
+ end
+
defp do_transform(%{"tuple" => entity}) do
Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
end
@@ -149,4 +154,9 @@ defp do_transform_string(value) do
do: String.to_existing_atom("Elixir." <> value),
else: value
end
+
+ defp do_eval(entity) do
+ cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
+ Code.eval_string(cleaned_string, [], requires: [], macros: [])
+ end
end
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index fcc000969..94462c3dd 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -439,6 +439,13 @@ def maybe_notify_mentioned_recipients(
def maybe_notify_mentioned_recipients(recipients, _), do: recipients
+ # Do not notify subscribers if author is making a reply
+ def maybe_notify_subscribers(recipients, %Activity{
+ object: %Object{data: %{"inReplyTo" => _ap_id}}
+ }) do
+ recipients
+ end
+
def maybe_notify_subscribers(
recipients,
%Activity{data: %{"actor" => actor, "type" => type}} = activity
diff --git a/mix.exs b/mix.exs
index c12b0a500..e69940c5d 100644
--- a/mix.exs
+++ b/mix.exs
@@ -143,7 +143,7 @@ defp deps do
{:telemetry, "~> 0.3"},
{:prometheus_ex, "~> 3.0"},
{:prometheus_plugs, "~> 1.1"},
- {:prometheus_phoenix, "~> 1.2"},
+ {:prometheus_phoenix, "~> 1.3"},
{:prometheus_ecto, "~> 1.4"},
{:recon, github: "ferd/recon", tag: "2.4.0"},
{:quack, "~> 0.1.1"},
diff --git a/test/notification_test.exs b/test/notification_test.exs
index dda570b49..28f8df49d 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -42,6 +42,28 @@ test "it creates a notification for subscribed users" do
assert notification.user_id == subscriber.id
end
+
+ test "does not create a notification for subscribed users if status is a reply" do
+ user = insert(:user)
+ other_user = insert(:user)
+ subscriber = insert(:user)
+
+ User.subscribe(subscriber, other_user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
+
+ {:ok, _reply_activity} =
+ CommonAPI.post(other_user, %{
+ "status" => "test reply",
+ "in_reply_to_status_id" => activity.id
+ })
+
+ user_notifications = Notification.for_user(user)
+ assert length(user_notifications) == 1
+
+ subscriber_notifications = Notification.for_user(subscriber)
+ assert Enum.empty?(subscriber_notifications)
+ end
end
describe "create_notification" do
diff --git a/test/plugs/authentication_plug_test.exs b/test/plugs/authentication_plug_test.exs
index 7ca045616..f7f8fd9f3 100644
--- a/test/plugs/authentication_plug_test.exs
+++ b/test/plugs/authentication_plug_test.exs
@@ -9,7 +9,6 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest do
alias Pleroma.User
import ExUnit.CaptureLog
- import Mock
setup %{conn: conn} do
user = %User{
@@ -67,13 +66,12 @@ test "check pbkdf2 hash" do
refute AuthenticationPlug.checkpw("test-password1", hash)
end
+ @tag :skip_on_mac
test "check sha512-crypt hash" do
hash =
"$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
- with_mock :crypt, crypt: fn _password, password_hash -> password_hash end do
- assert AuthenticationPlug.checkpw("password", hash)
- end
+ assert AuthenticationPlug.checkpw("password", hash)
end
test "it returns false when hash invalid" do
diff --git a/test/plugs/legacy_authentication_plug_test.exs b/test/plugs/legacy_authentication_plug_test.exs
index 02f530058..9804e073b 100644
--- a/test/plugs/legacy_authentication_plug_test.exs
+++ b/test/plugs/legacy_authentication_plug_test.exs
@@ -5,19 +5,18 @@
defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
use Pleroma.Web.ConnCase
+ import Pleroma.Factory
+
alias Pleroma.Plugs.LegacyAuthenticationPlug
alias Pleroma.User
- import Mock
-
setup do
- # password is "password"
- user = %User{
- id: 1,
- name: "dude",
- password_hash:
- "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
- }
+ user =
+ insert(:user,
+ password: "password",
+ password_hash:
+ "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
+ )
%{user: user}
end
@@ -36,6 +35,7 @@ test "it does nothing if a user is assigned", %{conn: conn, user: user} do
assert ret_conn == conn
end
+ @tag :skip_on_mac
test "it authenticates the auth_user if present and password is correct and resets the password",
%{
conn: conn,
@@ -46,22 +46,12 @@ test "it authenticates the auth_user if present and password is correct and rese
|> assign(:auth_credentials, %{username: "dude", password: "password"})
|> assign(:auth_user, user)
- conn =
- with_mocks([
- {:crypt, [], [crypt: fn _password, password_hash -> password_hash end]},
- {User, [],
- [
- reset_password: fn user, %{password: password, password_confirmation: password} ->
- {:ok, user}
- end
- ]}
- ]) do
- LegacyAuthenticationPlug.call(conn, %{})
- end
+ conn = LegacyAuthenticationPlug.call(conn, %{})
- assert conn.assigns.user == user
+ assert conn.assigns.user.id == user.id
end
+ @tag :skip_on_mac
test "it does nothing if the password is wrong", %{
conn: conn,
user: user
diff --git a/test/signature_test.exs b/test/signature_test.exs
index 7400cae9a..26337eaf9 100644
--- a/test/signature_test.exs
+++ b/test/signature_test.exs
@@ -48,16 +48,14 @@ test "it returns key" do
test "it returns error when not found user" do
assert capture_log(fn ->
- assert Signature.fetch_public_key(make_fake_conn("test-ap_id")) ==
- {:error, :error}
+ assert Signature.fetch_public_key(make_fake_conn("test-ap_id")) == {:error, :error}
end) =~ "[error] Could not decode user"
end
test "it returns error if public key is empty" do
user = insert(:user, %{info: %{source_data: %{"publicKey" => %{}}}})
- assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) ==
- {:error, :error}
+ assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == {:error, :error}
end
end
@@ -65,8 +63,7 @@ test "it returns error if public key is empty" do
test "it returns key" do
ap_id = "https://mastodon.social/users/lambadalambda"
- assert Signature.refetch_public_key(make_fake_conn(ap_id)) ==
- {:ok, @rsa_public_key}
+ assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key}
end
test "it returns error when not found user" do
@@ -105,4 +102,16 @@ test "it returns error" do
) == {:error, []}
end
end
+
+ describe "key_id_to_actor_id/1" do
+ test "it properly deduces the actor id for misskey" do
+ assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") ==
+ "https://example.com/users/1234"
+ end
+
+ test "it properly deduces the actor id for mastodon and pleroma" do
+ assert Signature.key_id_to_actor_id("https://example.com/users/1234#main-key") ==
+ "https://example.com/users/1234"
+ end
+ end
end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 531eb81e4..1f4239213 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -118,17 +118,20 @@ def direct_note_activity_factory do
def note_activity_factory(attrs \\ %{}) do
user = attrs[:user] || insert(:user)
note = attrs[:note] || insert(:note, user: user)
- attrs = Map.drop(attrs, [:user, :note])
+ data_attrs = attrs[:data_attrs] || %{}
+ attrs = Map.drop(attrs, [:user, :note, :data_attrs])
- data = %{
- "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
- "type" => "Create",
- "actor" => note.data["actor"],
- "to" => note.data["to"],
- "object" => note.data["id"],
- "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
- "context" => note.data["context"]
- }
+ data =
+ %{
+ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
+ "type" => "Create",
+ "actor" => note.data["actor"],
+ "to" => note.data["to"],
+ "object" => note.data["id"],
+ "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
+ "context" => note.data["context"]
+ }
+ |> Map.merge(data_attrs)
%Pleroma.Activity{
data: data,
diff --git a/test/test_helper.exs b/test/test_helper.exs
index 3e33f0335..a927b2c3d 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -2,7 +2,8 @@
# Copyright © 2017-2018 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
-ExUnit.start()
+os_exclude = if :os.type() == {:unix, :darwin}, do: [skip_on_mac: true], else: []
+ExUnit.start(exclude: os_exclude)
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
Mox.defmock(Pleroma.ReverseProxy.ClientMock, for: Pleroma.ReverseProxy.Client)
{:ok, _} = Application.ensure_all_started(:ex_machina)
diff --git a/test/user_test.exs b/test/user_test.exs
index 908f72a0e..8a7b7537f 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -824,6 +824,48 @@ test "blocks domains" do
assert User.blocks?(user, collateral_user)
end
+ test "does not block domain with same end" do
+ user = insert(:user)
+
+ collateral_user =
+ insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"})
+
+ {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com")
+
+ refute User.blocks?(user, collateral_user)
+ end
+
+ test "does not block domain with same end if wildcard added" do
+ user = insert(:user)
+
+ collateral_user =
+ insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"})
+
+ {:ok, user} = User.block_domain(user, "*.awful-and-rude-instance.com")
+
+ refute User.blocks?(user, collateral_user)
+ end
+
+ test "blocks domain with wildcard for subdomain" do
+ user = insert(:user)
+
+ user_from_subdomain =
+ insert(:user, %{ap_id: "https://subdomain.awful-and-rude-instance.com/user/bully"})
+
+ user_with_two_subdomains =
+ insert(:user, %{
+ ap_id: "https://subdomain.second_subdomain.awful-and-rude-instance.com/user/bully"
+ })
+
+ user_domain = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
+
+ {:ok, user} = User.block_domain(user, "*.awful-and-rude-instance.com")
+
+ assert User.blocks?(user, user_from_subdomain)
+ assert User.blocks?(user, user_with_two_subdomains)
+ assert User.blocks?(user, user_domain)
+ end
+
test "unblocks domains" do
user = insert(:user)
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index 00adbc0f9..1c0b274cb 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -6,11 +6,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Builders.ActivityBuilder
- alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Publisher
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
@@ -1083,113 +1081,6 @@ test "it can create a Flag activity" do
} = activity
end
- describe "publish_one/1" do
- test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
-
- assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
-
- assert called(Instances.set_reachable(inbox))
- end
-
- test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
-
- assert {:ok, _} =
- Publisher.publish_one(%{
- inbox: inbox,
- json: "{}",
- actor: actor,
- id: 1,
- unreachable_since: NaiveDateTime.utc_now()
- })
-
- assert called(Instances.set_reachable(inbox))
- end
-
- test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
-
- assert {:ok, _} =
- Publisher.publish_one(%{
- inbox: inbox,
- json: "{}",
- actor: actor,
- id: 1,
- unreachable_since: nil
- })
-
- refute called(Instances.set_reachable(inbox))
- end
-
- test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://404.site/users/nick1/inbox"
-
- assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
-
- assert called(Instances.set_unreachable(inbox))
- end
-
- test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://connrefused.site/users/nick1/inbox"
-
- assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
-
- assert called(Instances.set_unreachable(inbox))
- end
-
- test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://200.site/users/nick1/inbox"
-
- assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
-
- refute called(Instances.set_unreachable(inbox))
- end
-
- test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
- Instances,
- [:passthrough],
- [] do
- actor = insert(:user)
- inbox = "http://connrefused.site/users/nick1/inbox"
-
- assert {:error, _} =
- Publisher.publish_one(%{
- inbox: inbox,
- json: "{}",
- actor: actor,
- id: 1,
- unreachable_since: NaiveDateTime.utc_now()
- })
-
- refute called(Instances.set_unreachable(inbox))
- end
- end
-
test "fetch_activities/2 returns activities addressed to a list " do
user = insert(:user)
member = insert(:user)
diff --git a/test/web/activity_pub/mrf/mrf_test.exs b/test/web/activity_pub/mrf/mrf_test.exs
new file mode 100644
index 000000000..a9cdf5317
--- /dev/null
+++ b/test/web/activity_pub/mrf/mrf_test.exs
@@ -0,0 +1,46 @@
+defmodule Pleroma.Web.ActivityPub.MRFTest do
+ use ExUnit.Case, async: true
+ alias Pleroma.Web.ActivityPub.MRF
+
+ test "subdomains_regex/1" do
+ assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [
+ ~r/^unsafe.tld$/,
+ ~r/^(.*\.)*unsafe.tld$/
+ ]
+ end
+
+ describe "subdomain_match/2" do
+ test "common domains" do
+ regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"])
+
+ assert regexes == [~r/^unsafe.tld$/, ~r/^unsafe2.tld$/]
+
+ assert MRF.subdomain_match?(regexes, "unsafe.tld")
+ assert MRF.subdomain_match?(regexes, "unsafe2.tld")
+
+ refute MRF.subdomain_match?(regexes, "example.com")
+ end
+
+ test "wildcard domains with one subdomain" do
+ regexes = MRF.subdomains_regex(["*.unsafe.tld"])
+
+ assert regexes == [~r/^(.*\.)*unsafe.tld$/]
+
+ assert MRF.subdomain_match?(regexes, "unsafe.tld")
+ assert MRF.subdomain_match?(regexes, "sub.unsafe.tld")
+ refute MRF.subdomain_match?(regexes, "anotherunsafe.tld")
+ refute MRF.subdomain_match?(regexes, "unsafe.tldanother")
+ end
+
+ test "wildcard domains with two subdomains" do
+ regexes = MRF.subdomains_regex(["*.unsafe.tld"])
+
+ assert regexes == [~r/^(.*\.)*unsafe.tld$/]
+
+ assert MRF.subdomain_match?(regexes, "unsafe.tld")
+ assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld")
+ refute MRF.subdomain_match?(regexes, "sub.anotherunsafe.tld")
+ refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother")
+ end
+ end
+end
diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs
index 0fd68e103..8e86d2219 100644
--- a/test/web/activity_pub/mrf/simple_policy_test.exs
+++ b/test/web/activity_pub/mrf/simple_policy_test.exs
@@ -49,6 +49,19 @@ test "has a matching host" do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :media_removal], ["*.remote.instance"])
+ media_message = build_media_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(media_message) ==
+ {:ok,
+ media_message
+ |> Map.put("object", Map.delete(media_message["object"], "attachment"))}
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
end
describe "when :media_nsfw" do
@@ -74,6 +87,20 @@ test "has a matching host" do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :media_nsfw], ["*.remote.instance"])
+ media_message = build_media_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(media_message) ==
+ {:ok,
+ media_message
+ |> put_in(["object", "tag"], ["foo", "nsfw"])
+ |> put_in(["object", "sensitive"], true)}
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
end
defp build_media_message do
@@ -106,6 +133,15 @@ test "has a matching host" do
assert SimplePolicy.filter(report_message) == {:reject, nil}
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :report_removal], ["*.remote.instance"])
+ report_message = build_report_message()
+ local_message = build_local_message()
+
+ assert SimplePolicy.filter(report_message) == {:reject, nil}
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
end
defp build_report_message do
@@ -146,6 +182,27 @@ test "has a matching host" do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
+ test "match with wildcard domain" do
+ {actor, ftl_message} = build_ftl_actor_and_message()
+
+ ftl_message_actor_host =
+ ftl_message
+ |> Map.fetch!("actor")
+ |> URI.parse()
+ |> Map.fetch!(:host)
+
+ Config.put([:mrf_simple, :federated_timeline_removal], ["*." <> ftl_message_actor_host])
+ local_message = build_local_message()
+
+ assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
+ assert actor.follower_address in ftl_message["to"]
+ refute actor.follower_address in ftl_message["cc"]
+ refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
+ assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ end
+
test "has a matching host but only as:Public in to" do
{_actor, ftl_message} = build_ftl_actor_and_message()
@@ -192,6 +249,14 @@ test "has a matching host" do
assert SimplePolicy.filter(remote_message) == {:reject, nil}
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :reject], ["*.remote.instance"])
+
+ remote_message = build_remote_message()
+
+ assert SimplePolicy.filter(remote_message) == {:reject, nil}
+ end
end
describe "when :accept" do
@@ -224,6 +289,16 @@ test "has a matching host" do
assert SimplePolicy.filter(local_message) == {:ok, local_message}
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :accept], ["*.remote.instance"])
+
+ local_message = build_local_message()
+ remote_message = build_remote_message()
+
+ assert SimplePolicy.filter(local_message) == {:ok, local_message}
+ assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
+ end
end
describe "when :avatar_removal" do
@@ -251,6 +326,15 @@ test "has a matching host" do
refute filtered["icon"]
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :avatar_removal], ["*.remote.instance"])
+
+ remote_user = build_remote_user()
+ {:ok, filtered} = SimplePolicy.filter(remote_user)
+
+ refute filtered["icon"]
+ end
end
describe "when :banner_removal" do
@@ -278,6 +362,15 @@ test "has a matching host" do
refute filtered["image"]
end
+
+ test "match with wildcard domain" do
+ Config.put([:mrf_simple, :banner_removal], ["*.remote.instance"])
+
+ remote_user = build_remote_user()
+ {:ok, filtered} = SimplePolicy.filter(remote_user)
+
+ refute filtered["image"]
+ end
end
defp build_local_message do
diff --git a/test/web/activity_pub/publisher_test.exs b/test/web/activity_pub/publisher_test.exs
new file mode 100644
index 000000000..36a39c84c
--- /dev/null
+++ b/test/web/activity_pub/publisher_test.exs
@@ -0,0 +1,266 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.PublisherTest do
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+ import Tesla.Mock
+ import Mock
+
+ alias Pleroma.Activity
+ alias Pleroma.Instances
+ alias Pleroma.Web.ActivityPub.Publisher
+
+ @as_public "https://www.w3.org/ns/activitystreams#Public"
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+ :ok
+ end
+
+ describe "determine_inbox/2" do
+ test "it returns sharedInbox for messages involving as:Public in to" do
+ user =
+ insert(:user, %{
+ info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+ })
+
+ activity = %Activity{
+ data: %{"to" => [@as_public], "cc" => [user.follower_address]}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
+ end
+
+ test "it returns sharedInbox for messages involving as:Public in cc" do
+ user =
+ insert(:user, %{
+ info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+ })
+
+ activity = %Activity{
+ data: %{"cc" => [@as_public], "to" => [user.follower_address]}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
+ end
+
+ test "it returns sharedInbox for messages involving multiple recipients in to" do
+ user =
+ insert(:user, %{
+ info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+ })
+
+ user_two = insert(:user)
+ user_three = insert(:user)
+
+ activity = %Activity{
+ data: %{"cc" => [], "to" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
+ end
+
+ test "it returns sharedInbox for messages involving multiple recipients in cc" do
+ user =
+ insert(:user, %{
+ info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+ })
+
+ user_two = insert(:user)
+ user_three = insert(:user)
+
+ activity = %Activity{
+ data: %{"to" => [], "cc" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
+ end
+
+ test "it returns sharedInbox for messages involving multiple recipients in total" do
+ user =
+ insert(:user, %{
+ info: %{
+ source_data: %{
+ "inbox" => "http://example.com/personal-inbox",
+ "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
+ }
+ }
+ })
+
+ user_two = insert(:user)
+
+ activity = %Activity{
+ data: %{"to" => [user_two.ap_id], "cc" => [user.ap_id]}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
+ end
+
+ test "it returns inbox for messages involving single recipients in total" do
+ user =
+ insert(:user, %{
+ info: %{
+ source_data: %{
+ "inbox" => "http://example.com/personal-inbox",
+ "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
+ }
+ }
+ })
+
+ activity = %Activity{
+ data: %{"to" => [user.ap_id], "cc" => []}
+ }
+
+ assert Publisher.determine_inbox(activity, user) == "http://example.com/personal-inbox"
+ end
+ end
+
+ describe "publish_one/1" do
+ test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ assert called(Instances.set_reachable(inbox))
+ end
+
+ test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} =
+ Publisher.publish_one(%{
+ inbox: inbox,
+ json: "{}",
+ actor: actor,
+ id: 1,
+ unreachable_since: NaiveDateTime.utc_now()
+ })
+
+ assert called(Instances.set_reachable(inbox))
+ end
+
+ test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} =
+ Publisher.publish_one(%{
+ inbox: inbox,
+ json: "{}",
+ actor: actor,
+ id: 1,
+ unreachable_since: nil
+ })
+
+ refute called(Instances.set_reachable(inbox))
+ end
+
+ test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://404.site/users/nick1/inbox"
+
+ assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ assert called(Instances.set_unreachable(inbox))
+ end
+
+ test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://connrefused.site/users/nick1/inbox"
+
+ assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ assert called(Instances.set_unreachable(inbox))
+ end
+
+ test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ refute called(Instances.set_unreachable(inbox))
+ end
+
+ test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://connrefused.site/users/nick1/inbox"
+
+ assert {:error, _} =
+ Publisher.publish_one(%{
+ inbox: inbox,
+ json: "{}",
+ actor: actor,
+ id: 1,
+ unreachable_since: NaiveDateTime.utc_now()
+ })
+
+ refute called(Instances.set_unreachable(inbox))
+ end
+ end
+
+ describe "publish/2" do
+ test_with_mock "publishes an activity with BCC to all relevant peers.",
+ Pleroma.Web.Federator.Publisher,
+ [:passthrough],
+ [] do
+ follower =
+ insert(:user,
+ local: false,
+ info: %{
+ ap_enabled: true,
+ source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"}
+ }
+ )
+
+ actor = insert(:user, follower_address: follower.ap_id)
+ user = insert(:user)
+
+ {:ok, _follower_one} = Pleroma.User.follow(follower, actor)
+ actor = refresh_record(actor)
+
+ note_activity =
+ insert(:note_activity,
+ recipients: [follower.ap_id],
+ data_attrs: %{"bcc" => [user.ap_id]}
+ )
+
+ res = Publisher.publish(actor, note_activity)
+ assert res == :ok
+
+ assert called(
+ Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
+ inbox: "https://domain.com/users/nick1/inbox",
+ actor: actor,
+ id: note_activity.data["id"]
+ })
+ )
+ end
+ end
+end
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index 03aa46cae..1306c341d 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -1571,7 +1571,8 @@ test "common config example", %{conn: conn} do
%{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
%{"tuple" => [":seconds_valid", 60]},
%{"tuple" => [":path", ""]},
- %{"tuple" => [":key1", nil]}
+ %{"tuple" => [":key1", nil]},
+ %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}
]
}
]
@@ -1587,7 +1588,8 @@ test "common config example", %{conn: conn} do
%{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
%{"tuple" => [":seconds_valid", 60]},
%{"tuple" => [":path", ""]},
- %{"tuple" => [":key1", nil]}
+ %{"tuple" => [":key1", nil]},
+ %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}
]
}
]
diff --git a/test/web/admin_api/config_test.exs b/test/web/admin_api/config_test.exs
index d41666ef3..3190dc1c8 100644
--- a/test/web/admin_api/config_test.exs
+++ b/test/web/admin_api/config_test.exs
@@ -238,6 +238,14 @@ test "simple keyword" do
assert Config.from_binary(binary) == [key: "value"]
end
+ test "keyword with partial_chain key" do
+ binary =
+ Config.transform([%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}])
+
+ assert binary == :erlang.term_to_binary(partial_chain: &:hackney_connect.partial_chain/1)
+ assert Config.from_binary(binary) == [partial_chain: &:hackney_connect.partial_chain/1]
+ end
+
test "keyword" do
binary =
Config.transform([
diff --git a/test/web/federator_test.exs b/test/web/federator_test.exs
index 69dd4d747..6e143eee4 100644
--- a/test/web/federator_test.exs
+++ b/test/web/federator_test.exs
@@ -22,6 +22,15 @@ defmodule Pleroma.Web.FederatorTest do
:ok
end
+ describe "Publisher.perform" do
+ test "call `perform` with unknown task" do
+ assert {
+ :error,
+ "Don't know what to do with this"
+ } = Pleroma.Web.Federator.Publisher.perform("test", :ok, :ok)
+ end
+ end
+
describe "Publish an activity" do
setup do
user = insert(:user)
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index a3e4c4136..bc3213e0c 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -3786,6 +3786,20 @@ test "does not return users who have favorited the status but are blocked", %{
assert Enum.empty?(response)
end
+
+ test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
+ other_user = insert(:user)
+ {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+
+ response =
+ conn
+ |> assign(:user, nil)
+ |> get("/api/v1/statuses/#{activity.id}/favourited_by")
+ |> json_response(:ok)
+
+ [%{"id" => id}] = response
+ assert id == other_user.id
+ end
end
describe "GET /api/v1/statuses/:id/reblogged_by" do
@@ -3843,6 +3857,20 @@ test "does not return users who have reblogged the status but are blocked", %{
assert Enum.empty?(response)
end
+
+ test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
+ other_user = insert(:user)
+ {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
+
+ response =
+ conn
+ |> assign(:user, nil)
+ |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
+ |> json_response(:ok)
+
+ [%{"id" => id}] = response
+ assert id == other_user.id
+ end
end
describe "POST /auth/password, with valid parameters" do