Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop

This commit is contained in:
sadposter 2019-07-23 20:29:00 +01:00
commit ef7466921b
26 changed files with 727 additions and 191 deletions

View file

@ -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 - 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 - Existing user id not being preserved on insert conflict
- Rich Media: Parser failing when no TTL can be found by image TTL setters - 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 ### Added
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`) - MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
- MRF: Support for excluding specific domains from Transparency. - MRF: Support for excluding specific domains from Transparency.
- MRF: Support for filtering posts based on who they mention (`Pleroma.Web.ActivityPub.MRF.MentionPolicy`) - 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 - 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: 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 - Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header

View file

@ -564,6 +564,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
## `/api/pleroma/admin/config` ## `/api/pleroma/admin/config`
### List config settings ### List config settings
List config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.
- Method `GET` - Method `GET`
- Params: none - Params: none
- Response: - Response:
@ -582,6 +583,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
## `/api/pleroma/admin/config` ## `/api/pleroma/admin/config`
### Update config settings ### 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"`. 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"`. Atom keys and values can be passed with `:` in the beginning, e.g. `":upload"`.
Tuples can be passed as `{"tuple": ["first_val", Pleroma.Module, []]}`. Tuples can be passed as `{"tuple": ["first_val", Pleroma.Module, []]}`.

View file

@ -10,9 +10,18 @@ defmodule Pleroma.Signature do
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
def key_id_to_actor_id(key_id) do def key_id_to_actor_id(key_id) do
uri =
URI.parse(key_id) URI.parse(key_id)
|> Map.put(:fragment, nil) |> Map.put(:fragment, nil)
|> URI.to_string()
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 end
def fetch_public_key(conn) do def fetch_public_key(conn) do

View file

@ -586,12 +586,23 @@ def get_followers_query(user, page) do
@spec get_followers_query(User.t()) :: Ecto.Query.t() @spec get_followers_query(User.t()) :: Ecto.Query.t()
def get_followers_query(user), do: get_followers_query(user, nil) 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 def get_followers(user, page \\ nil) do
q = get_followers_query(user, page) q = get_followers_query(user, page)
{:ok, Repo.all(q)} {:ok, Repo.all(q)}
end 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 def get_followers_ids(user, page \\ nil) do
q = get_followers_query(user, page) 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 def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
blocks = info.blocks 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) %{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 end
def blocks?(nil, _), do: false
def subscribed_to?(user, %{ap_id: ap_id}) do def subscribed_to?(user, %{ap_id: ap_id}) do
with %User{} = target <- get_cached_by_ap_id(ap_id) do with %User{} = target <- get_cached_by_ap_id(ap_id) do
Enum.member?(target.info.subscribers, user.ap_id) Enum.member?(target.info.subscribers, user.ap_id)

View file

@ -25,4 +25,14 @@ def get_policies do
defp get_policies(policy) when is_atom(policy), do: [policy] defp get_policies(policy) when is_atom(policy), do: [policy]
defp get_policies(policies) when is_list(policies), do: policies defp get_policies(policies) when is_list(policies), do: policies
defp get_policies(_), do: [] 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 end

View file

@ -4,22 +4,29 @@
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
@moduledoc "Filter activities depending on their origin instance" @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 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 cond do
accepts == [] -> {:ok, object} accepts == [] -> {:ok, object}
actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {: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} true -> {:reject, nil}
end end
end end
defp check_reject(%{host: actor_host} = _actor_info, object) do 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} {:reject, nil}
else else
{:ok, object} {:ok, object}
@ -31,8 +38,12 @@ defp check_media_removal(
%{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object %{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object
) )
when length(child_attachment) > 0 do when length(child_attachment) > 0 do
media_removal =
Pleroma.Config.get([:mrf_simple, :media_removal])
|> MRF.subdomains_regex()
object = 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") child_object = Map.delete(object["object"], "attachment")
Map.put(object, "object", child_object) Map.put(object, "object", child_object)
else else
@ -51,8 +62,12 @@ defp check_media_nsfw(
"object" => child_object "object" => child_object
} = object } = object
) do ) do
media_nsfw =
Pleroma.Config.get([:mrf_simple, :media_nsfw])
|> MRF.subdomains_regex()
object = 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"] tags = (child_object["tag"] || []) ++ ["nsfw"]
child_object = Map.put(child_object, "tag", tags) child_object = Map.put(child_object, "tag", tags)
child_object = Map.put(child_object, "sensitive", true) 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_media_nsfw(_actor_info, object), do: {:ok, object}
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do 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 = object =
with true <- with true <- MRF.subdomain_match?(timeline_removal, actor_host),
Enum.member?(
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]),
actor_host
),
user <- User.get_cached_by_ap_id(object["actor"]), user <- User.get_cached_by_ap_id(object["actor"]),
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do
to = to =
@ -94,7 +109,11 @@ defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
end end
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do 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} {:reject, nil}
else else
{:ok, object} {: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_report_removal(_actor_info, object), do: {:ok, object}
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do 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")} {:ok, Map.delete(object, "icon")}
else else
{:ok, object} {: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_avatar_removal(_actor_info, object), do: {:ok, object}
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do 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")} {:ok, Map.delete(object, "image")}
else else
{:ok, object} {:ok, object}

View file

@ -87,18 +87,23 @@ defp should_federate?(inbox, public) do
if public do if public do
true true
else else
inbox_info = URI.parse(inbox) %{host: host} = URI.parse(inbox)
!Enum.member?(Config.get([:instance, :quarantined_instances], []), inbox_info.host)
quarantined_instances =
Config.get([:instance, :quarantined_instances], [])
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
end end
end end
@spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
defp recipients(actor, activity) do defp recipients(actor, activity) do
followers = {:ok, followers} =
if actor.follower_address in activity.recipients do if actor.follower_address in activity.recipients do
{:ok, followers} = User.get_followers(actor) User.get_external_followers(actor)
Enum.filter(followers, &(!&1.local))
else else
[] {:ok, []}
end end
Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers 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) |> Enum.map(& &1.ap_id)
end 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 """ @doc """
Publishes an activity with BCC to all relevant peers. Publishes an activity with BCC to all relevant peers.
""" """
@ -166,8 +210,8 @@ def publish(%User{} = actor, %Activity{} = activity) do
recipients(actor, activity) recipients(actor, activity)
|> Enum.filter(fn user -> User.ap_enabled?(user) end) |> Enum.filter(fn user -> User.ap_enabled?(user) end)
|> Enum.map(fn %{info: %{source_data: data}} -> |> Enum.map(fn %User{} = user ->
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"] determine_inbox(activity, user)
end) end)
|> Enum.uniq() |> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end) |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)

View file

@ -8,14 +8,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User 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: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data) def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{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?(%{"directMessage" => true}), do: false
def is_public?(data), do: @public in (data["to"] ++ (data["cc"] || []))
def is_public?(data) do
"https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
end
def is_private?(activity) do def is_private?(activity) do
with false <- is_public?(activity), with false <- is_public?(activity),
@ -69,15 +69,14 @@ def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
end end
def get_visibility(object) do def get_visibility(object) do
public = "https://www.w3.org/ns/activitystreams#Public"
to = object.data["to"] || [] to = object.data["to"] || []
cc = object.data["cc"] || [] cc = object.data["cc"] || []
cond do cond do
public in to -> @public in to ->
"public" "public"
public in cc -> @public in cc ->
"unlisted" "unlisted"
# this should use the sql for the object's activity # this should use the sql for the object's activity

View file

@ -84,6 +84,7 @@ defp do_convert(entity) when is_map(entity) do
end end
defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]} 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), defp do_convert(entity) when is_tuple(entity),
do: %{"tuple" => do_convert(Tuple.to_list(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(%Regex{} = entity) when is_map(entity), do: entity
defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do
cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "") {dispatch_settings, []} = do_eval(entity)
{dispatch_settings, []} = Code.eval_string(cleaned_string, [], requires: [], macros: [])
{:dispatch, [dispatch_settings]} {:dispatch, [dispatch_settings]}
end 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 defp do_transform(%{"tuple" => entity}) do
Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end) Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
end end
@ -149,4 +154,9 @@ defp do_transform_string(value) do
do: String.to_existing_atom("Elixir." <> value), do: String.to_existing_atom("Elixir." <> value),
else: value else: value
end end
defp do_eval(entity) do
cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
Code.eval_string(cleaned_string, [], requires: [], macros: [])
end
end end

View file

@ -439,6 +439,13 @@ def maybe_notify_mentioned_recipients(
def maybe_notify_mentioned_recipients(recipients, _), do: 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( def maybe_notify_subscribers(
recipients, recipients,
%Activity{data: %{"actor" => actor, "type" => type}} = activity %Activity{data: %{"actor" => actor, "type" => type}} = activity

View file

@ -143,7 +143,7 @@ defp deps do
{:telemetry, "~> 0.3"}, {:telemetry, "~> 0.3"},
{:prometheus_ex, "~> 3.0"}, {:prometheus_ex, "~> 3.0"},
{:prometheus_plugs, "~> 1.1"}, {:prometheus_plugs, "~> 1.1"},
{:prometheus_phoenix, "~> 1.2"}, {:prometheus_phoenix, "~> 1.3"},
{:prometheus_ecto, "~> 1.4"}, {:prometheus_ecto, "~> 1.4"},
{:recon, github: "ferd/recon", tag: "2.4.0"}, {:recon, github: "ferd/recon", tag: "2.4.0"},
{:quack, "~> 0.1.1"}, {:quack, "~> 0.1.1"},

View file

@ -42,6 +42,28 @@ test "it creates a notification for subscribed users" do
assert notification.user_id == subscriber.id assert notification.user_id == subscriber.id
end 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 end
describe "create_notification" do describe "create_notification" do

View file

@ -9,7 +9,6 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest do
alias Pleroma.User alias Pleroma.User
import ExUnit.CaptureLog import ExUnit.CaptureLog
import Mock
setup %{conn: conn} do setup %{conn: conn} do
user = %User{ user = %User{
@ -67,14 +66,13 @@ test "check pbkdf2 hash" do
refute AuthenticationPlug.checkpw("test-password1", hash) refute AuthenticationPlug.checkpw("test-password1", hash)
end end
@tag :skip_on_mac
test "check sha512-crypt hash" do test "check sha512-crypt hash" do
hash = hash =
"$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
with_mock :crypt, crypt: fn _password, password_hash -> password_hash end do
assert AuthenticationPlug.checkpw("password", hash) assert AuthenticationPlug.checkpw("password", hash)
end end
end
test "it returns false when hash invalid" do test "it returns false when hash invalid" do
hash = hash =

View file

@ -5,19 +5,18 @@
defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
import Pleroma.Factory
alias Pleroma.Plugs.LegacyAuthenticationPlug alias Pleroma.Plugs.LegacyAuthenticationPlug
alias Pleroma.User alias Pleroma.User
import Mock
setup do setup do
# password is "password" user =
user = %User{ insert(:user,
id: 1, password: "password",
name: "dude",
password_hash: password_hash:
"$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
} )
%{user: user} %{user: user}
end end
@ -36,6 +35,7 @@ test "it does nothing if a user is assigned", %{conn: conn, user: user} do
assert ret_conn == conn assert ret_conn == conn
end end
@tag :skip_on_mac
test "it authenticates the auth_user if present and password is correct and resets the password", test "it authenticates the auth_user if present and password is correct and resets the password",
%{ %{
conn: conn, 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_credentials, %{username: "dude", password: "password"})
|> assign(:auth_user, user) |> assign(:auth_user, user)
conn = conn = LegacyAuthenticationPlug.call(conn, %{})
with_mocks([
{:crypt, [], [crypt: fn _password, password_hash -> password_hash end]}, assert conn.assigns.user.id == user.id
{User, [],
[
reset_password: fn user, %{password: password, password_confirmation: password} ->
{:ok, user}
end
]}
]) do
LegacyAuthenticationPlug.call(conn, %{})
end
assert conn.assigns.user == user
end end
@tag :skip_on_mac
test "it does nothing if the password is wrong", %{ test "it does nothing if the password is wrong", %{
conn: conn, conn: conn,
user: user user: user

View file

@ -48,16 +48,14 @@ test "it returns key" do
test "it returns error when not found user" do test "it returns error when not found user" do
assert capture_log(fn -> assert capture_log(fn ->
assert Signature.fetch_public_key(make_fake_conn("test-ap_id")) == assert Signature.fetch_public_key(make_fake_conn("test-ap_id")) == {:error, :error}
{:error, :error}
end) =~ "[error] Could not decode user" end) =~ "[error] Could not decode user"
end end
test "it returns error if public key is empty" do test "it returns error if public key is empty" do
user = insert(:user, %{info: %{source_data: %{"publicKey" => %{}}}}) user = insert(:user, %{info: %{source_data: %{"publicKey" => %{}}}})
assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == {:error, :error}
{:error, :error}
end end
end end
@ -65,8 +63,7 @@ test "it returns error if public key is empty" do
test "it returns key" do test "it returns key" do
ap_id = "https://mastodon.social/users/lambadalambda" ap_id = "https://mastodon.social/users/lambadalambda"
assert Signature.refetch_public_key(make_fake_conn(ap_id)) == assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key}
{:ok, @rsa_public_key}
end end
test "it returns error when not found user" do test "it returns error when not found user" do
@ -105,4 +102,16 @@ test "it returns error" do
) == {:error, []} ) == {:error, []}
end end
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 end

View file

@ -118,9 +118,11 @@ def direct_note_activity_factory do
def note_activity_factory(attrs \\ %{}) do def note_activity_factory(attrs \\ %{}) do
user = attrs[:user] || insert(:user) user = attrs[:user] || insert(:user)
note = attrs[:note] || insert(:note, user: 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 = %{ data =
%{
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
"type" => "Create", "type" => "Create",
"actor" => note.data["actor"], "actor" => note.data["actor"],
@ -129,6 +131,7 @@ def note_activity_factory(attrs \\ %{}) do
"published" => DateTime.utc_now() |> DateTime.to_iso8601(), "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
"context" => note.data["context"] "context" => note.data["context"]
} }
|> Map.merge(data_attrs)
%Pleroma.Activity{ %Pleroma.Activity{
data: data, data: data,

View file

@ -2,7 +2,8 @@
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # 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) Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
Mox.defmock(Pleroma.ReverseProxy.ClientMock, for: Pleroma.ReverseProxy.Client) Mox.defmock(Pleroma.ReverseProxy.ClientMock, for: Pleroma.ReverseProxy.Client)
{:ok, _} = Application.ensure_all_started(:ex_machina) {:ok, _} = Application.ensure_all_started(:ex_machina)

View file

@ -824,6 +824,48 @@ test "blocks domains" do
assert User.blocks?(user, collateral_user) assert User.blocks?(user, collateral_user)
end 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 test "unblocks domains" do
user = insert(:user) user = insert(:user)
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})

View file

@ -6,11 +6,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
use Pleroma.DataCase use Pleroma.DataCase
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Builders.ActivityBuilder alias Pleroma.Builders.ActivityBuilder
alias Pleroma.Instances
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Publisher
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
@ -1083,113 +1081,6 @@ test "it can create a Flag activity" do
} = activity } = activity
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
test "fetch_activities/2 returns activities addressed to a list " do test "fetch_activities/2 returns activities addressed to a list " do
user = insert(:user) user = insert(:user)
member = insert(:user) member = insert(:user)

View file

@ -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

View file

@ -49,6 +49,19 @@ test "has a matching host" do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end 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 end
describe "when :media_nsfw" do describe "when :media_nsfw" do
@ -74,6 +87,20 @@ test "has a matching host" do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end 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 end
defp build_media_message do 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(report_message) == {:reject, nil}
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end 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 end
defp build_report_message do defp build_report_message do
@ -146,6 +182,27 @@ test "has a matching host" do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end 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 test "has a matching host but only as:Public in to" do
{_actor, ftl_message} = build_ftl_actor_and_message() {_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} assert SimplePolicy.filter(remote_message) == {:reject, nil}
end 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 end
describe "when :accept" do 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(local_message) == {:ok, local_message}
assert SimplePolicy.filter(remote_message) == {:ok, remote_message} assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
end 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 end
describe "when :avatar_removal" do describe "when :avatar_removal" do
@ -251,6 +326,15 @@ test "has a matching host" do
refute filtered["icon"] refute filtered["icon"]
end 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 end
describe "when :banner_removal" do describe "when :banner_removal" do
@ -278,6 +362,15 @@ test "has a matching host" do
refute filtered["image"] refute filtered["image"]
end 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 end
defp build_local_message do defp build_local_message do

View file

@ -0,0 +1,266 @@
# Pleroma: A lightweight social networking server
# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
# 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

View file

@ -1571,7 +1571,8 @@ test "common config example", %{conn: conn} do
%{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]}, %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
%{"tuple" => [":seconds_valid", 60]}, %{"tuple" => [":seconds_valid", 60]},
%{"tuple" => [":path", ""]}, %{"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" => [":method", "Pleroma.Captcha.Kocaptcha"]},
%{"tuple" => [":seconds_valid", 60]}, %{"tuple" => [":seconds_valid", 60]},
%{"tuple" => [":path", ""]}, %{"tuple" => [":path", ""]},
%{"tuple" => [":key1", nil]} %{"tuple" => [":key1", nil]},
%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}
] ]
} }
] ]

View file

@ -238,6 +238,14 @@ test "simple keyword" do
assert Config.from_binary(binary) == [key: "value"] assert Config.from_binary(binary) == [key: "value"]
end 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 test "keyword" do
binary = binary =
Config.transform([ Config.transform([

View file

@ -22,6 +22,15 @@ defmodule Pleroma.Web.FederatorTest do
:ok :ok
end 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 describe "Publish an activity" do
setup do setup do
user = insert(:user) user = insert(:user)

View file

@ -3786,6 +3786,20 @@ test "does not return users who have favorited the status but are blocked", %{
assert Enum.empty?(response) assert Enum.empty?(response)
end 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 end
describe "GET /api/v1/statuses/:id/reblogged_by" do 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) assert Enum.empty?(response)
end 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 end
describe "POST /auth/password, with valid parameters" do describe "POST /auth/password, with valid parameters" do