Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
48 KB
Referenced Files
None
Subscribers
None
diff --git a/config/test.exs b/config/test.exs
index fefdc2bbc..6446ad49f 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -1,210 +1,211 @@
import Config
# We don't run a server during test. If one is required,
# you can enable the server option below.
config :pleroma, Pleroma.Web.Endpoint,
http: [port: 4001],
url: [port: 4001],
server: true
# Disable captha for tests
config :pleroma, Pleroma.Captcha,
# It should not be enabled for automatic tests
enabled: false,
# A fake captcha service for tests
method: Pleroma.Captcha.Mock
# Print only warnings and errors during test
config :logger, :console,
level: :warning,
format: "\n[$level] $message\n"
config :pleroma, :auth, oauth_consumer_strategies: []
config :pleroma, Pleroma.Upload,
filters: [],
link_name: false,
default_description: :filename
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Test, enabled: true
config :pleroma, :instance,
email: "admin@example.com",
notify_email: "noreply@example.com",
skip_thread_containment: false,
federating: false,
external_user_synchronization: false,
static_dir: "test/instance_static/"
config :pleroma, :activitypub,
sign_object_fetches: false,
follow_handshake_timeout: 0,
client_api_enabled: true
# Configure your database
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
username: "postgres",
password: "postgres",
database: "pleroma_test",
hostname: System.get_env("DB_HOST") || "localhost",
port: System.get_env("DB_PORT") || "5432",
pool: Ecto.Adapters.SQL.Sandbox,
pool_size: System.schedulers_online() * 2,
log: false
config :pleroma, :dangerzone, override_repo_pool_size: true
# Reduce hash rounds for testing
config :pleroma, :password, iterations: 1
config :tesla, adapter: Tesla.Mock
config :pleroma, :rich_media,
enabled: false,
ignore_hosts: [],
ignore_tld: ["local", "localdomain", "lan"],
max_body: 2_000_000
config :pleroma, :instance,
multi_factor_authentication: [
totp: [
# digits 6 or 8
digits: 6,
period: 30
],
backup_codes: [
number: 2,
length: 6
]
]
config :web_push_encryption, :vapid_details,
subject: "mailto:administrator@example.com",
public_key:
"BLH1qVhJItRGCfxgTtONfsOKDc9VRAraXw-3NsmjMngWSh7NxOizN6bkuRA7iLTMPS82PjwJAr3UoK9EC1IFrz4",
private_key: "_-XZ0iebPrRfZ_o0-IatTdszYa8VCH1yLN-JauK7HHA"
config :pleroma, Oban, testing: :manual
config :pleroma, Pleroma.ScheduledActivity,
daily_user_limit: 2,
total_user_limit: 3,
enabled: false
config :pleroma, :rate_limit, %{}
config :pleroma, :http_security, report_uri: "https://endpoint.com"
config :pleroma, :http, send_user_agent: false
rum_enabled = System.get_env("RUM_ENABLED") == "true"
config :pleroma, :database, rum_enabled: rum_enabled
IO.puts("RUM enabled: #{rum_enabled}")
config :joken, default_signer: "yU8uHKq+yyAkZ11Hx//jcdacWc8yQ1bxAAGrplzB0Zwwjkp35v0RK9SO8WTPr6QZ"
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
config :pleroma, :modules, runtime_dir: "test/fixtures/modules"
config :pleroma, Pleroma.Gun, Pleroma.GunMock
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: true
config :pleroma, Pleroma.Web.Plugs.RemoteIp, enabled: false
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
config :tzdata, :autoupdate, :disabled
config :pleroma, :mrf, policies: []
config :pleroma, :pipeline,
object_validator: Pleroma.Web.ActivityPub.ObjectValidatorMock,
mrf: Pleroma.Web.ActivityPub.MRFMock,
activity_pub: Pleroma.Web.ActivityPub.ActivityPubMock,
side_effects: Pleroma.Web.ActivityPub.SideEffectsMock,
federator: Pleroma.Web.FederatorMock,
config: Pleroma.ConfigMock
config :pleroma, :cachex, provider: Pleroma.CachexMock
config :pleroma, Pleroma.Web.WebFinger, update_nickname_on_user_fetch: false
config :pleroma, :side_effects,
ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock,
logger: Pleroma.LoggerMock
config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch
config :pleroma, Pleroma.Search.Meilisearch, url: "http://127.0.0.1:7700/", private_key: nil
# Reduce recompilation time
# https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects
config :phoenix, :plug_init_mode, :runtime
config :pleroma, :config_impl, Pleroma.UnstubbedConfigMock
config :pleroma, :datetime_impl, Pleroma.DateTimeMock
config :pleroma, Pleroma.PromEx, disabled: true
# Mox definitions. Only read during compile time.
config :pleroma, Pleroma.User.Backup, config_impl: Pleroma.UnstubbedConfigMock
config :pleroma, Pleroma.Uploaders.S3, ex_aws_impl: Pleroma.Uploaders.S3.ExAwsMock
config :pleroma, Pleroma.Uploaders.S3, config_impl: Pleroma.UnstubbedConfigMock
config :pleroma, Pleroma.Upload, config_impl: Pleroma.UnstubbedConfigMock
config :pleroma, Pleroma.Language.LanguageDetector, config_impl: Pleroma.StaticStubbedConfigMock
config :pleroma, Pleroma.ScheduledActivity, config_impl: Pleroma.UnstubbedConfigMock
config :pleroma, Pleroma.Web.RichMedia.Helpers, config_impl: Pleroma.StaticStubbedConfigMock
config :pleroma, Pleroma.Uploaders.IPFS, config_impl: Pleroma.UnstubbedConfigMock
config :pleroma, Pleroma.Web.Plugs.HTTPSecurityPlug, config_impl: Pleroma.StaticStubbedConfigMock
config :pleroma, Pleroma.Web.Plugs.HTTPSignaturePlug, config_impl: Pleroma.StaticStubbedConfigMock
config :pleroma, Pleroma.Upload.Filter.AnonymizeFilename,
config_impl: Pleroma.StaticStubbedConfigMock
config :pleroma, Pleroma.Upload.Filter.Mogrify, config_impl: Pleroma.StaticStubbedConfigMock
config :pleroma, Pleroma.Upload.Filter.Mogrify, mogrify_impl: Pleroma.MogrifyMock
config :pleroma, Pleroma.Signature, http_signatures_impl: Pleroma.StubbedHTTPSignaturesMock
+config :pleroma, Pleroma.Web.ActivityPub.Publisher, signature_impl: Pleroma.SignatureMock
peer_module =
if String.to_integer(System.otp_release()) >= 25 do
:peer
else
:slave
end
config :pleroma, Pleroma.Cluster, peer_module: peer_module
config :pleroma, Pleroma.Application,
background_migrators: false,
internal_fetch: false,
load_custom_modules: false,
max_restarts: 100,
streamer_registry: false,
test_http_pools: true
config :pleroma, Pleroma.Web.Streaming, sync_streaming: true
config :pleroma, Pleroma.Uploaders.Uploader, timeout: 1_000
config :pleroma, Pleroma.Emoji.Loader, test_emoji: true
config :pleroma, Pleroma.Web.RichMedia.Backfill,
stream_out: Pleroma.Web.ActivityPub.ActivityPubMock
config :pleroma, Pleroma.Web.Plugs.HTTPSecurityPlug, enable: false
config :pleroma, Pleroma.User.Backup, tempdir: "test/tmp"
if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs"
else
IO.puts(
"You may want to create test.secret.exs to declare custom database connection parameters."
)
end
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
index 195513478..47d9d46f6 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -1,140 +1,141 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Signature do
+ @behaviour Pleroma.Signature.API
@behaviour HTTPSignatures.Adapter
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Keys
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
import Plug.Conn, only: [put_req_header: 3]
@http_signatures_impl Application.compile_env(
:pleroma,
[__MODULE__, :http_signatures_impl],
HTTPSignatures
)
@known_suffixes ["/publickey", "/main-key"]
def key_id_to_actor_id(key_id) do
uri =
key_id
|> URI.parse()
|> Map.put(:fragment, nil)
|> remove_suffix(@known_suffixes)
maybe_ap_id = URI.to_string(uri)
case ObjectValidators.ObjectID.cast(maybe_ap_id) do
{:ok, ap_id} ->
{:ok, ap_id}
_ ->
case Pleroma.Web.WebFinger.finger(maybe_ap_id) do
{:ok, %{"ap_id" => ap_id}} -> {:ok, ap_id}
_ -> {:error, maybe_ap_id}
end
end
end
defp remove_suffix(uri, [test | rest]) do
if not is_nil(uri.path) and String.ends_with?(uri.path, test) do
Map.put(uri, :path, String.replace(uri.path, test, ""))
else
remove_suffix(uri, rest)
end
end
defp remove_suffix(uri, []), do: uri
def fetch_public_key(conn) do
with {:ok, actor_id} <- get_actor_id(conn),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key}
else
e ->
{:error, e}
end
end
def refetch_public_key(conn) do
with {:ok, actor_id} <- get_actor_id(conn),
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key}
else
e ->
{:error, e}
end
end
def get_actor_id(conn) do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
{:ok, actor_id} <- key_id_to_actor_id(kid) do
{:ok, actor_id}
else
e ->
{:error, e}
end
end
def sign(%User{keys: keys} = user, headers) do
with {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
end
end
def signed_date, do: signed_date(NaiveDateTime.utc_now())
def signed_date(%NaiveDateTime{} = date) do
Timex.format!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
end
@spec validate_signature(Plug.Conn.t(), String.t()) :: boolean()
def validate_signature(%Plug.Conn{} = conn, request_target) do
# Newer drafts for HTTP signatures now use @request-target instead of the
# old (request-target). We'll now support both for incoming signatures.
conn =
conn
|> put_req_header("(request-target)", request_target)
|> put_req_header("@request-target", request_target)
@http_signatures_impl.validate_conn(conn)
end
@spec validate_signature(Plug.Conn.t()) :: boolean()
def validate_signature(%Plug.Conn{} = conn) do
# This (request-target) is non-standard, but many implementations do it
# this way due to a misinterpretation of
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-06
# "path" was interpreted as not having the query, though later examples
# show that it must be the absolute path + query. This behavior is kept to
# make sure most software (Pleroma itself, Mastodon, and probably others)
# do not break.
request_target = Enum.join([String.downcase(conn.method), conn.request_path], " ")
# This is the proper way to build the @request-target, as expected by
# many HTTP signature libraries, clarified in the following draft:
# https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-11.html#section-2.2.6
# It is the same as before, but containing the query part as well.
proper_target = Enum.join([request_target, "?", conn.query_string], "")
cond do
# Normal, non-standard behavior but expected by Pleroma and more.
validate_signature(conn, request_target) ->
true
# Has query string and the previous one failed: let's try the standard.
conn.query_string != "" ->
validate_signature(conn, proper_target)
# If there's no query string and signature fails, it's rotten.
true ->
false
end
end
end
diff --git a/lib/pleroma/signature/api.ex b/lib/pleroma/signature/api.ex
new file mode 100644
index 000000000..8b4f0c2e4
--- /dev/null
+++ b/lib/pleroma/signature/api.ex
@@ -0,0 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Signature.API do
+ @moduledoc """
+ Behaviour for signing requests and producing HTTP Date headers.
+
+ This is used to allow tests to replace the signing implementation with Mox.
+ """
+
+ @callback sign(user :: Pleroma.User.t(), headers :: map()) :: String.t()
+ @callback signed_date() :: String.t()
+end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 9b37712db..1d8896857 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -1,395 +1,401 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Delivery
alias Pleroma.HTTP
alias Pleroma.Instances
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Publisher.Prepared
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Workers.PublisherWorker
require Pleroma.Constants
import Pleroma.Web.ActivityPub.Visibility
require Logger
@moduledoc """
ActivityPub outgoing federation module.
"""
+ @signature_impl Application.compile_env(
+ :pleroma,
+ [__MODULE__, :signature_impl],
+ Pleroma.Signature
+ )
+
@doc """
Enqueue publishing a single activity.
"""
@spec enqueue_one(map(), Keyword.t()) :: {:ok, %Oban.Job{}}
def enqueue_one(%{} = params, worker_args \\ []) do
PublisherWorker.new(
%{"op" => "publish_one", "params" => params},
worker_args
)
|> Oban.insert()
end
@doc """
Gathers a set of remote users given an IR envelope.
"""
def remote_users(%User{id: user_id}, %{data: %{"to" => to} = data}) do
cc = Map.get(data, "cc", [])
bcc =
data
|> Map.get("bcc", [])
|> Enum.reduce([], fn ap_id, bcc ->
case Pleroma.List.get_by_ap_id(ap_id) do
%Pleroma.List{user_id: ^user_id} = list ->
{:ok, following} = Pleroma.List.get_following(list)
bcc ++ Enum.map(following, & &1.ap_id)
_ ->
bcc
end
end)
[to, cc, bcc]
|> Enum.concat()
|> Enum.map(&User.get_cached_by_ap_id/1)
|> Enum.filter(fn user -> user && !user.local end)
end
@doc """
Determine if an activity can be represented by running it through Transmogrifier.
"""
def representable?(%Activity{} = activity) do
with {:ok, _data} <- Transmogrifier.prepare_outgoing(activity.data) do
true
else
_e ->
false
end
end
@doc """
Prepare an activity for publishing from an Oban job
* `inbox`: the inbox to publish to
* `activity_id`: the internal activity id
* `cc`: the cc recipients relevant to this inbox (optional)
"""
@spec prepare_one(map()) :: Prepared.t()
def prepare_one(%{inbox: inbox, activity_id: activity_id} = params) do
activity = Activity.get_by_id_with_user_actor(activity_id)
actor = activity.user_actor
ap_id = activity.data["id"]
Logger.debug("Federating #{ap_id} to #{inbox}")
uri = %{path: path} = URI.parse(inbox)
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
{actor, data} =
with {_, false} <- {:actor_changed?, data["actor"] != activity.data["actor"]} do
{actor, data}
else
{:actor_changed?, true} ->
# If prepare_outgoing changes the actor, re-get it from the db
new_actor = User.get_cached_by_ap_id(data["actor"])
{new_actor, data}
end
param_cc = Map.get(params, :cc, [])
original_cc = Map.get(data, "cc", [])
public_address = Pleroma.Constants.as_public()
# Ensure unlisted posts don't lose the public address in the cc
# if the param_cc was set
cc =
if public_address in original_cc and public_address not in param_cc do
[public_address | param_cc]
else
param_cc
end
json =
data
|> Map.put("cc", cc)
|> Jason.encode!()
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
- date = Pleroma.Signature.signed_date()
+ date = @signature_impl.signed_date()
signature =
- Pleroma.Signature.sign(actor, %{
+ @signature_impl.sign(actor, %{
"(request-target)": "post #{path}",
host: signature_host(uri),
"content-length": byte_size(json),
digest: digest,
date: date
})
%Prepared{
activity_id: activity_id,
json: json,
date: date,
signature: signature,
digest: digest,
inbox: inbox,
unreachable_since: params[:unreachable_since]
}
end
@doc """
Publish a single message to a peer. Takes a struct with the following
parameters set:
* `activity_id`: the activity id
* `json`: the json payload
* `date`: the signed date from Pleroma.Signature.signed_date()
* `signature`: the signature from Pleroma.Signature.sign/2
* `digest`: base64 encoded the hash of the json payload prefixed with "SHA-256="
* `inbox`: the inbox URI of this delivery
* `unreachable_since`: timestamp the instance was marked unreachable
"""
def publish_one(%Prepared{} = p) do
with {:ok, %{status: code}} = result when code in 200..299 <-
HTTP.post(
p.inbox,
p.json,
[
{"Content-Type", "application/activity+json"},
{"Date", p.date},
{"signature", p.signature},
{"digest", p.digest}
]
) do
result
else
{_post_result, %{status: code} = response} = e ->
Logger.metadata(activity: p.activity_id, inbox: p.inbox, status: code)
Logger.error("Publisher failed to inbox #{p.inbox} with status #{code}")
case response do
%{status: 400} -> {:cancel, :bad_request}
%{status: 403} -> {:cancel, :forbidden}
%{status: 404} -> {:cancel, :not_found}
%{status: 410} -> {:cancel, :not_found}
_ -> {:error, e}
end
{:error, {:already_started, _}} ->
Logger.debug("Publisher snoozing worker job due worker :already_started race condition")
connection_pool_snooze()
{:error, :pool_full} ->
Logger.debug("Publisher snoozing worker job due to full connection pool")
connection_pool_snooze()
e ->
Logger.metadata(activity: p.activity_id, inbox: p.inbox)
Logger.error("Publisher failed to inbox #{p.inbox} #{inspect(e)}")
{:error, e}
end
end
defp connection_pool_snooze, do: {:snooze, 3}
defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
if port == URI.default_port(scheme) do
host
else
"#{host}:#{port}"
end
end
def should_federate?(nil, _), do: false
def should_federate?(_, true), do: true
def should_federate?(inbox, _) do
%{host: host} = URI.parse(inbox)
quarantined_instances =
Config.get([:instance, :quarantined_instances], [])
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
end
@spec recipients(User.t(), Activity.t()) :: [[User.t()]]
defp recipients(actor, activity) do
followers =
if actor.follower_address in activity.recipients do
User.get_external_followers(actor)
else
[]
end
fetchers =
with %Activity{data: %{"type" => "Delete"}} <- activity,
%Object{id: object_id} <- Object.normalize(activity, fetch: false),
fetchers <- User.get_delivered_users_by_object_id(object_id),
_ <- Delivery.delete_all_by_object_id(object_id) do
fetchers
else
_ ->
[]
end
mentioned = remote_users(actor, activity)
non_mentioned = (followers ++ fetchers) -- mentioned
[mentioned, non_mentioned]
end
defp get_cc_ap_ids(ap_id, recipients) do
host = Map.get(URI.parse(ap_id), :host)
recipients
|> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end)
|> Enum.map(& &1.ap_id)
end
defp maybe_use_sharedinbox(%User{shared_inbox: nil, inbox: inbox}), do: inbox
defp maybe_use_sharedinbox(%User{shared_inbox: shared_inbox}), do: shared_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{inbox: inbox} = user
) do
to = activity_data["to"] || []
cc = activity_data["cc"] || []
type = activity_data["type"]
cond do
type == "Delete" ->
maybe_use_sharedinbox(user)
Pleroma.Constants.as_public() in to || Pleroma.Constants.as_public() in cc ->
maybe_use_sharedinbox(user)
length(to) + length(cc) > 1 ->
maybe_use_sharedinbox(user)
true ->
inbox
end
end
@doc """
Publishes an activity with BCC to all relevant peers.
"""
def publish(%User{} = actor, %{data: %{"bcc" => bcc}} = activity)
when is_list(bcc) and bcc != [] do
public = public?(activity)
[priority_recipients, recipients] = recipients(actor, activity)
[priority_inboxes, other_inboxes] =
[priority_recipients, recipients]
|> Enum.map(fn recipients ->
recipients
|> Enum.map(fn %User{} = user ->
determine_inbox(activity, user)
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|> Instances.filter_reachable()
end)
Repo.checkout(fn ->
Enum.each([priority_inboxes, other_inboxes], fn inboxes ->
Enum.each(inboxes, fn inbox ->
%User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end)
# Get all the recipients on the same host and add them to cc. Otherwise, a remote
# instance would only accept a first message for the first recipient and ignore the rest.
cc = get_cc_ap_ids(ap_id, recipients)
__MODULE__.enqueue_one(%{
inbox: inbox,
cc: cc,
activity_id: activity.id
})
end)
end)
end)
end
# Publishes an activity to all relevant peers.
def publish(%User{} = actor, %Activity{} = activity) do
public = public?(activity)
if public && Config.get([:instance, :allow_relay]) do
Logger.debug(fn -> "Relaying #{activity.data["id"]} out" end)
Relay.publish(activity)
end
[priority_inboxes, inboxes] =
recipients(actor, activity)
|> Enum.map(fn recipients ->
recipients
|> Enum.map(fn %User{} = user ->
determine_inbox(activity, user)
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
end)
inboxes = inboxes -- priority_inboxes
[{priority_inboxes, 0}, {inboxes, 1}]
|> Enum.each(fn {inboxes, priority} ->
inboxes
|> Instances.filter_reachable()
|> Enum.each(fn inbox ->
__MODULE__.enqueue_one(
%{
inbox: inbox,
activity_id: activity.id
},
priority: priority
)
end)
end)
:ok
end
def gather_webfinger_links(%User{} = user) do
[
%{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id},
%{
"rel" => "self",
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href" => user.ap_id
},
%{
"rel" => "http://ostatus.org/schema/1.0/subscribe",
"template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
}
]
end
def gather_nodeinfo_protocol_names, do: ["activitypub"]
end
diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs
index 8da9df23c..4721b2432 100644
--- a/test/pleroma/web/activity_pub/publisher_test.exs
+++ b/test/pleroma/web/activity_pub/publisher_test.exs
@@ -1,595 +1,596 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.PublisherTest do
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.Web.ConnCase
import Pleroma.Factory
import Tesla.Mock
import Mock
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Tests.ObanHelpers
alias Pleroma.Web.ActivityPub.Publisher
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
@as_public "https://www.w3.org/ns/activitystreams#Public"
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
setup_all do: clear_config([:instance, :federating], true)
describe "should_federate?/1" do
test "it returns false when the inbox is nil" do
refute Publisher.should_federate?(nil, false)
refute Publisher.should_federate?(nil, true)
end
test "it returns true when public is true" do
assert Publisher.should_federate?(false, true)
end
end
describe "gather_webfinger_links/1" do
test "it returns links" do
user = insert(:user)
expected_links = [
%{"href" => user.ap_id, "rel" => "self", "type" => "application/activity+json"},
%{
"href" => user.ap_id,
"rel" => "self",
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
},
%{
"rel" => "http://ostatus.org/schema/1.0/subscribe",
"template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
}
]
assert expected_links == Publisher.gather_webfinger_links(user)
end
end
describe "determine_inbox/2" do
test "it returns sharedInbox for messages involving as:Public in to" do
user = insert(:user, %{shared_inbox: "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, %{shared_inbox: "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, %{shared_inbox: "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, %{shared_inbox: "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, %{
shared_inbox: "http://example.com/inbox",
inbox: "http://example.com/personal-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, %{
shared_inbox: "http://example.com/inbox",
inbox: "http://example.com/personal-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 "publish to url with with different ports" do
inbox80 = "http://42.site/users/nick1/inbox"
inbox42 = "http://42.site:42/users/nick1/inbox"
activity = insert(:note_activity)
mock(fn
%{method: :post, url: "http://42.site:42/users/nick1/inbox"} ->
{:ok, %Tesla.Env{status: 200, body: "port 42"}}
%{method: :post, url: "http://42.site/users/nick1/inbox"} ->
{:ok, %Tesla.Env{status: 200, body: "port 80"}}
end)
_actor = insert(:user)
assert {:ok, %{body: "port 42"}} =
Publisher.prepare_one(%{
inbox: inbox42,
activity_id: activity.id,
unreachable_since: true
})
|> Publisher.publish_one()
assert {:ok, %{body: "port 80"}} =
Publisher.prepare_one(%{
inbox: inbox80,
activity_id: activity.id,
unreachable_since: true
})
|> Publisher.publish_one()
end
end
describe "publish/2" do
test_with_mock "doesn't publish a non-public activity to quarantined instances.",
Pleroma.Web.ActivityPub.Publisher,
[:passthrough],
[] do
Config.put([:instance, :quarantined_instances], [{"domain.com", "some reason"}])
follower =
insert(:user, %{
local: false,
inbox: "https://domain.com/users/nick1/inbox"
})
actor = insert(:user, follower_address: follower.ap_id)
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
actor = refresh_record(actor)
note_activity =
insert(:followers_only_note_activity,
user: actor,
recipients: [follower.ap_id]
)
res = Publisher.publish(actor, note_activity)
assert res == :ok
refute_enqueued(
worker: "Pleroma.Workers.PublisherWorker",
args: %{
"params" => %{
inbox: "https://domain.com/users/nick1/inbox",
activity_id: note_activity.id
}
}
)
end
test_with_mock "Publishes a non-public activity to non-quarantined instances.",
Pleroma.Web.ActivityPub.Publisher,
[:passthrough],
[] do
Config.put([:instance, :quarantined_instances], [{"somedomain.com", "some reason"}])
follower =
insert(:user, %{
local: false,
inbox: "https://domain.com/users/nick1/inbox"
})
actor = insert(:user, follower_address: follower.ap_id)
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
actor = refresh_record(actor)
note_activity =
insert(:followers_only_note_activity,
user: actor,
recipients: [follower.ap_id]
)
res = Publisher.publish(actor, note_activity)
assert res == :ok
assert_enqueued(
worker: "Pleroma.Workers.PublisherWorker",
args: %{
"params" => %{
inbox: "https://domain.com/users/nick1/inbox",
activity_id: note_activity.id
}
},
priority: 1
)
end
test_with_mock "Publishes to directly addressed actors with higher priority.",
Pleroma.Web.ActivityPub.Publisher,
[:passthrough],
[] do
note_activity = insert(:direct_note_activity)
actor = Pleroma.User.get_by_ap_id(note_activity.data["actor"])
res = Publisher.publish(actor, note_activity)
assert res == :ok
assert called(
Publisher.enqueue_one(
%{
inbox: :_,
activity_id: note_activity.id
},
priority: 0
)
)
end
test_with_mock "Publishes with the new actor if prepare_outgoing changes the actor.",
Pleroma.Web.ActivityPub.Publisher,
[:passthrough],
[] do
mock(fn
%{method: :post, url: "https://domain.com/users/nick1/inbox", body: body} ->
{:ok, %Tesla.Env{status: 200, body: body}}
end)
other_user =
insert(:user, %{
local: false,
inbox: "https://domain.com/users/nick1/inbox"
})
actor = insert(:user)
replaced_actor = insert(:user)
note_activity =
insert(:note_activity,
user: actor,
data_attrs: %{"to" => [other_user.ap_id]}
)
with_mock Pleroma.Web.ActivityPub.Transmogrifier,
prepare_outgoing: fn data -> {:ok, Map.put(data, "actor", replaced_actor.ap_id)} end do
prepared =
Publisher.prepare_one(%{
inbox: "https://domain.com/users/nick1/inbox",
activity_id: note_activity.id,
cc: ["https://domain.com/users/nick2/inbox"]
})
{:ok, decoded} = Jason.decode(prepared.json)
assert decoded["actor"] == replaced_actor.ap_id
{:ok, published} = Publisher.publish_one(prepared)
sent_activity = Jason.decode!(published.body)
assert sent_activity["actor"] == replaced_actor.ap_id
end
end
test_with_mock "publishes an activity with BCC to all relevant peers.",
Pleroma.Web.ActivityPub.Publisher,
[:passthrough],
[] do
follower =
insert(:user, %{
local: false,
inbox: "https://domain.com/users/nick1/inbox"
})
actor = insert(:user, follower_address: follower.ap_id)
user = insert(:user)
{:ok, follower, actor} = Pleroma.User.follow(follower, 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_enqueued(
worker: "Pleroma.Workers.PublisherWorker",
args: %{
"params" => %{
inbox: "https://domain.com/users/nick1/inbox",
activity_id: note_activity.id
}
}
)
end
test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.",
Pleroma.Web.ActivityPub.Publisher,
[:passthrough],
[] do
fetcher =
insert(:user,
local: false,
inbox: "https://domain.com/users/nick1/inbox"
)
another_fetcher =
insert(:user,
local: false,
inbox: "https://domain2.com/users/nick1/inbox"
)
actor = insert(:user)
note_activity = insert(:note_activity, user: actor)
object = Object.normalize(note_activity, fetch: false)
activity_path = String.trim_leading(note_activity.data["id"], Pleroma.Web.Endpoint.url())
object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
build_conn()
|> put_req_header("accept", "application/activity+json")
|> assign(:user, fetcher)
|> get(object_path)
|> json_response(200)
build_conn()
|> put_req_header("accept", "application/activity+json")
|> assign(:user, another_fetcher)
|> get(activity_path)
|> json_response(200)
{:ok, delete} = CommonAPI.delete(note_activity.id, actor)
res = Publisher.publish(actor, delete)
assert res == :ok
assert_enqueued(
worker: "Pleroma.Workers.PublisherWorker",
args: %{
"params" => %{
inbox: "https://domain.com/users/nick1/inbox",
activity_id: delete.id
}
},
priority: 1
)
assert_enqueued(
worker: "Pleroma.Workers.PublisherWorker",
args: %{
"params" => %{
inbox: "https://domain2.com/users/nick1/inbox",
activity_id: delete.id
}
},
priority: 1
)
end
end
test "cc in prepared json for a follow request is an empty list" do
user = insert(:user)
remote_user = insert(:user, local: false)
{:ok, _, _, activity} = CommonAPI.follow(remote_user, user)
assert_enqueued(
worker: "Pleroma.Workers.PublisherWorker",
args: %{
"activity_id" => activity.id,
"op" => "publish"
}
)
ObanHelpers.perform_all()
expected_params =
%{
"activity_id" => activity.id,
"inbox" => remote_user.inbox,
"unreachable_since" => nil
}
assert_enqueued(
worker: "Pleroma.Workers.PublisherWorker",
args: %{
"op" => "publish_one",
"params" => expected_params
}
)
# params need to be atom keys for Publisher.prepare_one.
# this is done in the Oban job.
expected_params = Map.new(expected_params, fn {k, v} -> {String.to_atom(k), v} end)
%{json: json} = Publisher.prepare_one(expected_params)
{:ok, decoded} = Jason.decode(json)
assert decoded["cc"] == []
end
test "unlisted activities retain public address in cc" do
user = insert(:user)
# simulate unlistd activity by only having
# public address in cc
activity =
insert(:note_activity,
user: user,
data_attrs: %{
"cc" => [@as_public],
"to" => [user.follower_address]
}
)
assert @as_public in activity.data["cc"]
prepared =
Publisher.prepare_one(%{
inbox: "https://remote.instance/users/someone/inbox",
activity_id: activity.id
})
{:ok, decoded} = Jason.decode(prepared.json)
assert @as_public in decoded["cc"]
# maybe we also have another inbox in cc
# during Publishing
activity =
insert(:note_activity,
user: user,
data_attrs: %{
"cc" => [@as_public],
"to" => [user.follower_address]
}
)
prepared =
Publisher.prepare_one(%{
inbox: "https://remote.instance/users/someone/inbox",
activity_id: activity.id,
cc: ["https://remote.instance/users/someone_else/inbox"]
})
{:ok, decoded} = Jason.decode(prepared.json)
assert decoded["cc"] == [@as_public, "https://remote.instance/users/someone_else/inbox"]
end
test "public address in cc parameter is preserved" do
user = insert(:user)
cc_with_public = [@as_public, "https://example.org/users/other"]
activity =
insert(:note_activity,
user: user,
data_attrs: %{
"cc" => cc_with_public,
"to" => [user.follower_address]
}
)
assert @as_public in activity.data["cc"]
prepared =
Publisher.prepare_one(%{
inbox: "https://remote.instance/users/someone/inbox",
activity_id: activity.id,
cc: cc_with_public
})
{:ok, decoded} = Jason.decode(prepared.json)
assert cc_with_public == decoded["cc"]
end
test "cc parameter is preserved" do
user = insert(:user)
activity =
insert(:note_activity,
user: user,
data_attrs: %{
"cc" => ["https://example.com/specific/user"],
"to" => [user.follower_address]
}
)
prepared =
Publisher.prepare_one(%{
inbox: "https://remote.instance/users/someone/inbox",
activity_id: activity.id,
cc: ["https://example.com/specific/user"]
})
{:ok, decoded} = Jason.decode(prepared.json)
assert decoded["cc"] == ["https://example.com/specific/user"]
end
describe "prepare_one/1 with reporter anonymization" do
- test_with_mock "signs with the anonymized actor keys when Transmogrifier changes actor",
- Pleroma.Signature,
- [:passthrough],
- sign: fn %Pleroma.User{} = user, headers ->
- send(self(), {:signed_as, user.ap_id})
- "TESTSIG"
- end do
+ test "signs with the anonymized actor keys when Transmogrifier changes actor" do
+ Pleroma.SignatureMock
+ |> Mox.stub(:signed_date, fn -> Pleroma.Signature.signed_date() end)
+ |> Mox.expect(:sign, fn %Pleroma.User{} = user, _headers ->
+ send(self(), {:signed_as, user.ap_id})
+ "TESTSIG"
+ end)
+
placeholder = insert(:user)
reporter = insert(:user)
target_account = insert(:user)
clear_config([:activitypub, :anonymize_reporter], true)
clear_config([:activitypub, :anonymize_reporter_local_nickname], placeholder.nickname)
{:ok, reported} = CommonAPI.post(target_account, %{status: "content"})
context = Utils.generate_context_id()
{:ok, activity} =
ActivityPub.flag(%{
actor: reporter,
context: context,
account: target_account,
statuses: [reported],
content: "reason"
})
_prepared =
Publisher.prepare_one(%{
inbox: "http://remote.example/users/alice/inbox",
activity_id: activity.id
})
assert_received {:signed_as, ap_id}
assert ap_id == placeholder.ap_id
end
end
end
diff --git a/test/support/data_case.ex b/test/support/data_case.ex
index 304bee5da..e33ab4b34 100644
--- a/test/support/data_case.ex
+++ b/test/support/data_case.ex
@@ -1,148 +1,149 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.DataCase do
@moduledoc """
This module defines the setup for tests requiring
access to the application's data layer.
You may define functions here to be used as helpers in
your tests.
Finally, if the test case interacts with the database,
it cannot be async. For this reason, every test runs
inside a transaction which is reset at the beginning
of the test unless the test case is marked as async.
"""
use ExUnit.CaseTemplate
import Pleroma.Tests.Helpers, only: [clear_config: 2]
using do
quote do
alias Pleroma.Repo
import Ecto
import Ecto.Changeset
import Ecto.Query
import Pleroma.DataCase
use Pleroma.Tests.Helpers
# Sets up OAuth access with specified scopes
defp oauth_access(scopes, opts \\ []) do
user =
Keyword.get_lazy(opts, :user, fn ->
Pleroma.Factory.insert(:user)
end)
token =
Keyword.get_lazy(opts, :oauth_token, fn ->
Pleroma.Factory.insert(:oauth_token, user: user, scopes: scopes)
end)
%{user: user, token: token}
end
end
end
def clear_cachex do
Pleroma.Supervisor
|> Supervisor.which_children()
|> Enum.each(fn
{name, _, _, [Cachex]} ->
name
|> to_string
|> String.trim_leading("cachex_")
|> Kernel.<>("_cache")
|> String.to_existing_atom()
|> Cachex.clear()
_ ->
nil
end)
end
def setup_multi_process_mode(tags) do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
if tags[:async] do
Mox.stub_with(Pleroma.CachexMock, Pleroma.NullCache)
Mox.set_mox_private()
else
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, {:shared, self()})
Mox.set_mox_global()
Mox.stub_with(Pleroma.CachexMock, Pleroma.CachexProxy)
clear_cachex()
end
:ok
end
def setup_streamer(tags) do
if tags[:needs_streamer] do
start_supervised(%{
id: Pleroma.Web.Streamer.registry(),
start:
{Registry, :start_link, [[keys: :duplicate, name: Pleroma.Web.Streamer.registry()]]}
})
end
:ok
end
setup tags do
setup_multi_process_mode(tags)
setup_streamer(tags)
stub_pipeline()
Mox.verify_on_exit!()
:ok
end
def stub_pipeline do
Mox.stub_with(Pleroma.Web.ActivityPub.SideEffectsMock, Pleroma.Web.ActivityPub.SideEffects)
Mox.stub_with(
Pleroma.Web.ActivityPub.ObjectValidatorMock,
Pleroma.Web.ActivityPub.ObjectValidator
)
Mox.stub_with(Pleroma.Web.ActivityPub.MRFMock, Pleroma.Web.ActivityPub.MRF)
Mox.stub_with(Pleroma.Web.ActivityPub.ActivityPubMock, Pleroma.Web.ActivityPub.ActivityPub)
Mox.stub_with(Pleroma.Web.FederatorMock, Pleroma.Web.Federator)
Mox.stub_with(Pleroma.ConfigMock, Pleroma.Config)
Mox.stub_with(Pleroma.StaticStubbedConfigMock, Pleroma.Test.StaticConfig)
Mox.stub_with(Pleroma.StubbedHTTPSignaturesMock, Pleroma.Test.HTTPSignaturesProxy)
Mox.stub_with(Pleroma.DateTimeMock, Pleroma.DateTime.Impl)
+ Mox.stub_with(Pleroma.SignatureMock, Pleroma.Signature)
end
def ensure_local_uploader(context) do
test_uploader = Map.get(context, :uploader) || Pleroma.Uploaders.Local
clear_config([Pleroma.Upload, :uploader], test_uploader)
clear_config([Pleroma.Upload, :filters], [])
:ok
end
@doc """
A helper that transform changeset errors to a map of messages.
changeset = Accounts.create_user(%{password: "short"})
assert "password is too short" in errors_on(changeset).password
assert %{password: ["password is too short"]} = errors_on(changeset)
"""
def errors_on(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
Enum.reduce(opts, message, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end)
end
end
diff --git a/test/support/mocks.ex b/test/support/mocks.ex
index b26834871..25bcb6472 100644
--- a/test/support/mocks.ex
+++ b/test/support/mocks.ex
@@ -1,42 +1,44 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
Mox.defmock(Pleroma.CachexMock, for: Pleroma.Caching)
Mox.defmock(Pleroma.Web.ActivityPub.ObjectValidatorMock,
for: Pleroma.Web.ActivityPub.ObjectValidator.Validating
)
Mox.defmock(Pleroma.Web.ActivityPub.MRFMock,
for: Pleroma.Web.ActivityPub.MRF.PipelineFiltering
)
Mox.defmock(Pleroma.Web.ActivityPub.ActivityPubMock,
for: [
Pleroma.Web.ActivityPub.ActivityPub.Persisting,
Pleroma.Web.ActivityPub.ActivityPub.Streaming
]
)
Mox.defmock(Pleroma.Web.ActivityPub.SideEffectsMock,
for: Pleroma.Web.ActivityPub.SideEffects.Handling
)
Mox.defmock(Pleroma.Web.FederatorMock, for: Pleroma.Web.Federator.Publishing)
Mox.defmock(Pleroma.ConfigMock, for: Pleroma.Config.Getting)
Mox.defmock(Pleroma.UnstubbedConfigMock, for: Pleroma.Config.Getting)
Mox.defmock(Pleroma.StaticStubbedConfigMock, for: Pleroma.Config.Getting)
Mox.defmock(Pleroma.StubbedHTTPSignaturesMock, for: Pleroma.HTTPSignaturesAPI)
Mox.defmock(Pleroma.LoggerMock, for: Pleroma.Logging)
Mox.defmock(Pleroma.Uploaders.S3.ExAwsMock, for: Pleroma.Uploaders.S3.ExAwsAPI)
Mox.defmock(Pleroma.Language.LanguageDetectorMock,
for: Pleroma.Language.LanguageDetector.Provider
)
Mox.defmock(Pleroma.DateTimeMock, for: Pleroma.DateTime)
Mox.defmock(Pleroma.MogrifyMock, for: Pleroma.MogrifyBehaviour)
+
+Mox.defmock(Pleroma.SignatureMock, for: Pleroma.Signature.API)

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 30, 1:29 AM (1 d, 57 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1799108
Default Alt Text
(48 KB)

Event Timeline