summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog.d/oban-recevier-improvements.fix1
-rw-r--r--changelog.d/rich-media-no-heads.change1
-rw-r--r--lib/pleroma/http/adapter_helper.ex10
-rw-r--r--lib/pleroma/http/adapter_helper/finch.ex33
-rw-r--r--lib/pleroma/http/adapter_helper/gun.ex9
-rw-r--r--lib/pleroma/web/federator.ex8
-rw-r--r--lib/pleroma/web/rich_media/helpers.ex71
-rw-r--r--lib/pleroma/workers/receiver_worker.ex24
-rw-r--r--test/fixtures/bastianallgeier.json117
-rw-r--r--test/fixtures/receiver_worker_signature_activity.json127
-rw-r--r--test/pleroma/workers/receiver_worker_test.exs288
11 files changed, 365 insertions, 324 deletions
diff --git a/changelog.d/oban-recevier-improvements.fix b/changelog.d/oban-recevier-improvements.fix
new file mode 100644
index 000000000..f91502ed2
--- /dev/null
+++ b/changelog.d/oban-recevier-improvements.fix
@@ -0,0 +1 @@
+ReceiverWorker will cancel processing jobs instead of retrying if the user cannot be fetched due to 403, 404, or 410 errors or if the account is disabled locally.
diff --git a/changelog.d/rich-media-no-heads.change b/changelog.d/rich-media-no-heads.change
new file mode 100644
index 000000000..0bab323aa
--- /dev/null
+++ b/changelog.d/rich-media-no-heads.change
@@ -0,0 +1 @@
+Rich Media preview fetching will skip making an HTTP HEAD request to check a URL for allowed content type and length if the Tesla adapter is Gun or Finch
diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex
index dcb27a29d..32c1080f7 100644
--- a/lib/pleroma/http/adapter_helper.ex
+++ b/lib/pleroma/http/adapter_helper.ex
@@ -52,6 +52,7 @@ defmodule Pleroma.HTTP.AdapterHelper do
case adapter() do
Tesla.Adapter.Gun -> AdapterHelper.Gun
Tesla.Adapter.Hackney -> AdapterHelper.Hackney
+ {Tesla.Adapter.Finch, _} -> AdapterHelper.Finch
_ -> AdapterHelper.Default
end
end
@@ -118,4 +119,13 @@ defmodule Pleroma.HTTP.AdapterHelper do
host_charlist
end
end
+
+ @spec can_stream? :: bool()
+ def can_stream? do
+ case Application.get_env(:tesla, :adapter) do
+ Tesla.Adapter.Gun -> true
+ {Tesla.Adapter.Finch, _} -> true
+ _ -> false
+ end
+ end
end
diff --git a/lib/pleroma/http/adapter_helper/finch.ex b/lib/pleroma/http/adapter_helper/finch.ex
new file mode 100644
index 000000000..181caed7e
--- /dev/null
+++ b/lib/pleroma/http/adapter_helper/finch.ex
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.AdapterHelper.Finch do
+ @behaviour Pleroma.HTTP.AdapterHelper
+
+ alias Pleroma.Config
+ alias Pleroma.HTTP.AdapterHelper
+
+ @spec options(keyword(), URI.t()) :: keyword()
+ def options(incoming_opts \\ [], %URI{} = _uri) do
+ proxy =
+ [:http, :proxy_url]
+ |> Config.get()
+ |> AdapterHelper.format_proxy()
+
+ config_opts = Config.get([:http, :adapter], [])
+
+ config_opts
+ |> Keyword.merge(incoming_opts)
+ |> AdapterHelper.maybe_add_proxy(proxy)
+ |> maybe_stream()
+ end
+
+ # Finch uses [response: :stream]
+ defp maybe_stream(opts) do
+ case Keyword.pop(opts, :stream, nil) do
+ {true, opts} -> Keyword.put(opts, :response, :stream)
+ {_, opts} -> opts
+ end
+ end
+end
diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex
index 1fe8dd4b2..30ba26765 100644
--- a/lib/pleroma/http/adapter_helper/gun.ex
+++ b/lib/pleroma/http/adapter_helper/gun.ex
@@ -32,6 +32,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
|> AdapterHelper.maybe_add_proxy(proxy)
|> Keyword.merge(incoming_opts)
|> put_timeout()
+ |> maybe_stream()
end
defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
@@ -47,6 +48,14 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
Keyword.put(opts, :timeout, recv_timeout)
end
+ # Gun uses [body_as: :stream]
+ defp maybe_stream(opts) do
+ case Keyword.pop(opts, :stream, nil) do
+ {true, opts} -> Keyword.put(opts, :body_as, :stream)
+ {_, opts} -> opts
+ end
+ end
+
@spec pool_timeout(pool()) :: non_neg_integer()
def pool_timeout(pool) do
default = Config.get([:pools, :default, :recv_timeout], 5_000)
diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex
index 2df716556..58260afa8 100644
--- a/lib/pleroma/web/federator.ex
+++ b/lib/pleroma/web/federator.ex
@@ -102,7 +102,8 @@ defmodule Pleroma.Web.Federator do
# NOTE: we use the actor ID to do the containment, this is fine because an
# actor shouldn't be acting on objects outside their own AP server.
- with {_, {:ok, _user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)},
+ with {_, {:ok, user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)},
+ {:user_active, true} <- {:user_active, match?(true, user.is_active)},
nil <- Activity.normalize(params["id"]),
{_, :ok} <-
{:correct_origin?, Containment.contain_origin_from_id(actor, params)},
@@ -121,11 +122,6 @@ defmodule Pleroma.Web.Federator do
Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
{:error, e}
- {:error, {:validate_object, _}} = e ->
- Logger.error("Incoming AP doc validation error: #{inspect(e)}")
- Logger.debug(Jason.encode!(params, pretty: true))
- e
-
e ->
# Just drop those for now
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex
index e2889b351..d4be97957 100644
--- a/lib/pleroma/web/rich_media/helpers.ex
+++ b/lib/pleroma/web/rich_media/helpers.ex
@@ -11,16 +11,39 @@ defmodule Pleroma.Web.RichMedia.Helpers do
@spec rich_media_get(String.t()) :: {:ok, String.t()} | get_errors()
def rich_media_get(url) do
- headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
+ case Pleroma.HTTP.AdapterHelper.can_stream?() do
+ true -> stream(url)
+ false -> head_first(url)
+ end
+ |> handle_result(url)
+ end
+
+ defp stream(url) do
+ with {_, {:ok, %Tesla.Env{status: 200, body: stream_body, headers: headers}}} <-
+ {:get, Pleroma.HTTP.get(url, req_headers(), http_options())},
+ {_, :ok} <- {:content_type, check_content_type(headers)},
+ {_, :ok} <- {:content_length, check_content_length(headers)},
+ {:read_stream, {:ok, body}} <- {:read_stream, read_stream(stream_body)} do
+ {:ok, body}
+ end
+ end
+ defp head_first(url) do
with {_, {:ok, %Tesla.Env{status: 200, headers: headers}}} <-
- {:head, Pleroma.HTTP.head(url, headers, http_options())},
+ {:head, Pleroma.HTTP.head(url, req_headers(), http_options())},
{_, :ok} <- {:content_type, check_content_type(headers)},
{_, :ok} <- {:content_length, check_content_length(headers)},
{_, {:ok, %Tesla.Env{status: 200, body: body}}} <-
- {:get, Pleroma.HTTP.get(url, headers, http_options())} do
+ {:get, Pleroma.HTTP.get(url, req_headers(), http_options())} do
{:ok, body}
- else
+ end
+ end
+
+ defp handle_result(result, url) do
+ case result do
+ {:ok, body} ->
+ {:ok, body}
+
{:head, _} ->
Logger.debug("Rich media error for #{url}: HTTP HEAD failed")
{:error, :head}
@@ -29,8 +52,12 @@ defmodule Pleroma.Web.RichMedia.Helpers do
Logger.debug("Rich media error for #{url}: content-type is #{type}")
{:error, :content_type}
- {:content_length, {_, length}} ->
- Logger.debug("Rich media error for #{url}: content-length is #{length}")
+ {:content_length, :error} ->
+ Logger.debug("Rich media error for #{url}: content-length exceeded")
+ {:error, :body_too_large}
+
+ {:read_stream, :error} ->
+ Logger.debug("Rich media error for #{url}: content-length exceeded")
{:error, :body_too_large}
{:get, _} ->
@@ -59,7 +86,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
{_, maybe_content_length} ->
case Integer.parse(maybe_content_length) do
{content_length, ""} when content_length <= max_body -> :ok
- {_, ""} -> {:error, maybe_content_length}
+ {_, ""} -> :error
_ -> :ok
end
@@ -68,13 +95,37 @@ defmodule Pleroma.Web.RichMedia.Helpers do
end
end
- defp http_options do
- timeout = Config.get!([:rich_media, :timeout])
+ defp read_stream(stream) do
+ max_body = Keyword.get(http_options(), :max_body)
+
+ try do
+ result =
+ Stream.transform(stream, 0, fn chunk, total_bytes ->
+ new_total = total_bytes + byte_size(chunk)
+
+ if new_total > max_body do
+ raise("Exceeds max body limit of #{max_body}")
+ else
+ {[chunk], new_total}
+ end
+ end)
+ |> Enum.into(<<>>)
+ {:ok, result}
+ rescue
+ _ -> :error
+ end
+ end
+
+ defp http_options do
[
pool: :rich_media,
max_body: Config.get([:rich_media, :max_body], 5_000_000),
- tesla_middleware: [{Tesla.Middleware.Timeout, timeout: timeout}]
+ stream: true
]
end
+
+ defp req_headers do
+ [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
+ end
end
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index d4db97b63..0373ec15f 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -33,7 +33,7 @@ defmodule Pleroma.Workers.ReceiverWorker do
query_string: query_string
}
- with {:ok, %User{} = _actor} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]),
+ with {:ok, %User{}} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]),
{:ok, _public_key} <- Signature.refetch_public_key(conn_data),
{:signature, true} <- {:signature, Signature.validate_signature(conn_data)},
{:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
@@ -56,17 +56,29 @@ defmodule Pleroma.Workers.ReceiverWorker do
def timeout(_job), do: :timer.seconds(5)
+ defp process_errors({:error, {:error, _} = error}), do: process_errors(error)
+
defp process_errors(errors) do
case errors do
- {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
+ # User fetch failures
+ {:error, :not_found} = reason -> {:cancel, reason}
+ {:error, :forbidden} = reason -> {:cancel, reason}
+ # Inactive user
+ {:error, {:user_active, false} = reason} -> {:cancel, reason}
+ # Validator will error and return a changeset error
+ # e.g., duplicate activities or if the object was deleted
+ {:error, {:validate, {:error, _changeset} = reason}} -> {:cancel, reason}
+ # Duplicate detection during Normalization
{:error, :already_present} -> {:cancel, :already_present}
- {:error, {:validate_object, _} = reason} -> {:cancel, reason}
- {:error, {:error, {:validate, {:error, _changeset} = reason}}} -> {:cancel, reason}
+ # MRFs will return a reject
{:error, {:reject, _} = reason} -> {:cancel, reason}
+ # HTTP Sigs
{:signature, false} -> {:cancel, :invalid_signature}
- {:error, "Object has been deleted"} = reason -> {:cancel, reason}
+ # Origin / URL validation failed somewhere possibly due to spoofing
+ {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
+ # Unclear if this can be reached
{:error, {:side_effects, {:error, :no_object_actor}} = reason} -> {:cancel, reason}
- {:error, :not_found} = reason -> {:cancel, reason}
+ # Catchall
{:error, _} = e -> e
e -> {:error, e}
end
diff --git a/test/fixtures/bastianallgeier.json b/test/fixtures/bastianallgeier.json
deleted file mode 100644
index 6b47e7db9..000000000
--- a/test/fixtures/bastianallgeier.json
+++ /dev/null
@@ -1,117 +0,0 @@
-{
- "@context": [
- "https://www.w3.org/ns/activitystreams",
- "https://w3id.org/security/v1",
- {
- "Curve25519Key": "toot:Curve25519Key",
- "Device": "toot:Device",
- "Ed25519Key": "toot:Ed25519Key",
- "Ed25519Signature": "toot:Ed25519Signature",
- "EncryptedMessage": "toot:EncryptedMessage",
- "PropertyValue": "schema:PropertyValue",
- "alsoKnownAs": {
- "@id": "as:alsoKnownAs",
- "@type": "@id"
- },
- "cipherText": "toot:cipherText",
- "claim": {
- "@id": "toot:claim",
- "@type": "@id"
- },
- "deviceId": "toot:deviceId",
- "devices": {
- "@id": "toot:devices",
- "@type": "@id"
- },
- "discoverable": "toot:discoverable",
- "featured": {
- "@id": "toot:featured",
- "@type": "@id"
- },
- "featuredTags": {
- "@id": "toot:featuredTags",
- "@type": "@id"
- },
- "fingerprintKey": {
- "@id": "toot:fingerprintKey",
- "@type": "@id"
- },
- "focalPoint": {
- "@container": "@list",
- "@id": "toot:focalPoint"
- },
- "identityKey": {
- "@id": "toot:identityKey",
- "@type": "@id"
- },
- "indexable": "toot:indexable",
- "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
- "memorial": "toot:memorial",
- "messageFranking": "toot:messageFranking",
- "messageType": "toot:messageType",
- "movedTo": {
- "@id": "as:movedTo",
- "@type": "@id"
- },
- "publicKeyBase64": "toot:publicKeyBase64",
- "schema": "http://schema.org#",
- "suspended": "toot:suspended",
- "toot": "http://joinmastodon.org/ns#",
- "value": "schema:value"
- }
- ],
- "attachment": [
- {
- "name": "Website",
- "type": "PropertyValue",
- "value": "<a href=\"https://bastianallgeier.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">bastianallgeier.com</span><span class=\"invisible\"></span></a>"
- },
- {
- "name": "Project",
- "type": "PropertyValue",
- "value": "<a href=\"https://getkirby.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">getkirby.com</span><span class=\"invisible\"></span></a>"
- },
- {
- "name": "Github",
- "type": "PropertyValue",
- "value": "<a href=\"https://github.com/bastianallgeier\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">github.com/bastianallgeier</span><span class=\"invisible\"></span></a>"
- }
- ],
- "devices": "https://mastodon.social/users/bastianallgeier/collections/devices",
- "discoverable": true,
- "endpoints": {
- "sharedInbox": "https://mastodon.social/inbox"
- },
- "featured": "https://mastodon.social/users/bastianallgeier/collections/featured",
- "featuredTags": "https://mastodon.social/users/bastianallgeier/collections/tags",
- "followers": "https://mastodon.social/users/bastianallgeier/followers",
- "following": "https://mastodon.social/users/bastianallgeier/following",
- "icon": {
- "mediaType": "image/jpeg",
- "type": "Image",
- "url": "https://files.mastodon.social/accounts/avatars/000/007/393/original/0180a20079617c71.jpg"
- },
- "id": "https://mastodon.social/users/bastianallgeier",
- "image": {
- "mediaType": "image/jpeg",
- "type": "Image",
- "url": "https://files.mastodon.social/accounts/headers/000/007/393/original/13d644ab46d50478.jpeg"
- },
- "inbox": "https://mastodon.social/users/bastianallgeier/inbox",
- "indexable": false,
- "manuallyApprovesFollowers": false,
- "memorial": false,
- "name": "Bastian Allgeier",
- "outbox": "https://mastodon.social/users/bastianallgeier/outbox",
- "preferredUsername": "bastianallgeier",
- "publicKey": {
- "id": "https://mastodon.social/users/bastianallgeier#main-key",
- "owner": "https://mastodon.social/users/bastianallgeier",
- "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3fz+hpgVztO9z6HUhyzv\nwP++ERBBoIwSLKf1TyIM8bvzGFm2YXaO5uxu1HvumYFTYc3ACr3q4j8VUb7NMxkQ\nlzu4QwPjOFJ43O+fY+HSPORXEDW5fXDGC5DGpox4+i08LxRmx7L6YPRUSUuPN8nI\nWyq1Qsq1zOQrNY/rohMXkBdSXxqC3yIRqvtLt4otCgay/5tMogJWkkS6ZKyFhb9z\nwVVy1fsbV10c9C+SHy4NH26CKaTtpTYLRBMjhTCS8bX8iDSjGIf2aZgYs1ir7gEz\n9wf5CvLiENmVWGwm64t6KSEAkA4NJ1hzgHUZPCjPHZE2SmhO/oHaxokTzqtbbENJ\n1QIDAQAB\n-----END PUBLIC KEY-----\n"
- },
- "published": "2016-11-01T00:00:00Z",
- "summary": "<p>Designer &amp; developer. Creator of Kirby CMS</p>",
- "tag": [],
- "type": "Person",
- "url": "https://mastodon.social/@bastianallgeier"
-}
diff --git a/test/fixtures/receiver_worker_signature_activity.json b/test/fixtures/receiver_worker_signature_activity.json
index 3c3fb3fd2..19dc0087f 100644
--- a/test/fixtures/receiver_worker_signature_activity.json
+++ b/test/fixtures/receiver_worker_signature_activity.json
@@ -1,62 +1,109 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
{
+ "claim": {
+ "@id": "toot:claim",
+ "@type": "@id"
+ },
+ "memorial": "toot:memorial",
"atomUri": "ostatus:atomUri",
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"blurhash": "toot:blurhash",
- "conversation": "ostatus:conversation",
+ "ostatus": "http://ostatus.org#",
+ "discoverable": "toot:discoverable",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
},
- "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
- "ostatus": "http://ostatus.org#",
+ "votersCount": "toot:votersCount",
+ "Hashtag": "as:Hashtag",
+ "Emoji": "toot:Emoji",
+ "alsoKnownAs": {
+ "@id": "as:alsoKnownAs",
+ "@type": "@id"
+ },
"sensitive": "as:sensitive",
+ "movedTo": {
+ "@id": "as:movedTo",
+ "@type": "@id"
+ },
+ "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+ "conversation": "ostatus:conversation",
+ "Device": "toot:Device",
+ "schema": "http://schema.org#",
"toot": "http://joinmastodon.org/ns#",
- "votersCount": "toot:votersCount"
- }
- ],
- "atomUri": "https://chaos.social/users/distantnative/statuses/109336635639931467",
- "attachment": [
- {
- "blurhash": "UAK1zS00OXIUxuMxIUM{?b-:-;W:Di?b%2M{",
- "height": 960,
- "mediaType": "image/jpeg",
- "name": null,
- "type": "Document",
- "url": "https://assets.chaos.social/media_attachments/files/109/336/634/286/114/657/original/2e6122063d8bfb26.jpeg",
- "width": 346
+ "cipherText": "toot:cipherText",
+ "suspended": "toot:suspended",
+ "messageType": "toot:messageType",
+ "featuredTags": {
+ "@id": "toot:featuredTags",
+ "@type": "@id"
+ },
+ "Curve25519Key": "toot:Curve25519Key",
+ "deviceId": "toot:deviceId",
+ "Ed25519Signature": "toot:Ed25519Signature",
+ "featured": {
+ "@id": "toot:featured",
+ "@type": "@id"
+ },
+ "devices": {
+ "@id": "toot:devices",
+ "@type": "@id"
+ },
+ "value": "schema:value",
+ "PropertyValue": "schema:PropertyValue",
+ "messageFranking": "toot:messageFranking",
+ "publicKeyBase64": "toot:publicKeyBase64",
+ "identityKey": {
+ "@id": "toot:identityKey",
+ "@type": "@id"
+ },
+ "Ed25519Key": "toot:Ed25519Key",
+ "indexable": "toot:indexable",
+ "EncryptedMessage": "toot:EncryptedMessage",
+ "fingerprintKey": {
+ "@id": "toot:fingerprintKey",
+ "@type": "@id"
+ }
}
],
- "attributedTo": "https://chaos.social/users/distantnative",
- "cc": [
- "https://chaos.social/users/distantnative/followers"
- ],
- "content": "<p>Favorite piece of anthropology meta discourse.</p>",
- "contentMap": {
- "en": "<p>Favorite piece of anthropology meta discourse.</p>"
- },
- "conversation": "tag:chaos.social,2022-11-13:objectId=71843781:objectType=Conversation",
- "id": "https://chaos.social/users/distantnative/statuses/109336635639931467",
+ "actor": "https://phpc.social/users/denniskoch",
+ "cc": [],
+ "id": "https://phpc.social/users/denniskoch/statuses/112847382711461301/activity",
"inReplyTo": null,
"inReplyToAtomUri": null,
- "published": "2022-11-13T13:04:20Z",
- "replies": {
- "first": {
- "items": [],
- "next": "https://chaos.social/users/distantnative/statuses/109336635639931467/replies?only_other_accounts=true&page=true",
- "partOf": "https://chaos.social/users/distantnative/statuses/109336635639931467/replies",
- "type": "CollectionPage"
+ "object": {
+ "atomUri": "https://phpc.social/users/denniskoch/statuses/112847382711461301",
+ "attachment": [],
+ "attributedTo": "https://phpc.social/users/denniskoch",
+ "cc": [],
+ "content": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.</p>",
+ "contentMap": {
+ "en": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.</p>"
},
- "id": "https://chaos.social/users/distantnative/statuses/109336635639931467/replies",
- "type": "Collection"
+ "conversation": "tag:mastodon.social,2024-07-25:objectId=760068442:objectType=Conversation",
+ "id": "https://phpc.social/users/denniskoch/statuses/112847382711461301",
+ "published": "2024-07-25T13:33:29Z",
+ "replies": null,
+ "sensitive": false,
+ "tag": [],
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "type": "Note",
+ "url": "https://phpc.social/@denniskoch/112847382711461301"
+ },
+ "published": "2024-07-25T13:33:29Z",
+ "signature": {
+ "created": "2024-07-25T13:33:29Z",
+ "creator": "https://phpc.social/users/denniskoch#main-key",
+ "signatureValue": "slz9BKJzd2n1S44wdXGOU+bV/wsskdgAaUpwxj8R16mYOL8+DTpE6VnfSKoZGsBBJT8uG5gnVfVEz1YsTUYtymeUgLMh7cvd8VnJnZPS+oixbmBRVky/Myf91TEgQQE7G4vDmTdB4ii54hZrHcOOYYf5FKPNRSkMXboKA6LMqNtekhbI+JTUJYIB02WBBK6PUyo15f6B1RJ6HGWVgud9NE0y1EZXfrkqUt682p8/9D49ORf7AwjXUJibKic2RbPvhEBj70qUGfBm4vvgdWhSUn1IG46xh+U0+NrTSUED82j1ZVOeua/2k/igkGs8cSBkY35quXTkPz6gbqCCH66CuA==",
+ "type": "RsaSignature2017"
},
- "sensitive": false,
- "summary": null,
- "tag": [],
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
- "type": "Note",
- "url": "https://chaos.social/@distantnative/109336635639931467"
+ "type": "Create"
}
diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs
index 33be91085..4d53c44ed 100644
--- a/test/pleroma/workers/receiver_worker_test.exs
+++ b/test/pleroma/workers/receiver_worker_test.exs
@@ -9,6 +9,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
import Mock
import Pleroma.Factory
+ alias Pleroma.User
alias Pleroma.Web.Federator
alias Pleroma.Workers.ReceiverWorker
@@ -51,25 +52,106 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
})
end
- test "it can validate the signature" do
- Tesla.Mock.mock(fn
- %{url: "https://mastodon.social/users/bastianallgeier"} ->
- %Tesla.Env{
- status: 200,
- body: File.read!("test/fixtures/bastianallgeier.json"),
- headers: [{"content-type", "application/activity+json"}]
- }
+ describe "cancels on a failed user fetch" do
+ setup do
+ Tesla.Mock.mock(fn
+ %{url: "https://springfield.social/users/bart"} ->
+ %Tesla.Env{
+ status: 403,
+ body: ""
+ }
- %{url: "https://mastodon.social/users/bastianallgeier/collections/featured"} ->
- %Tesla.Env{
- status: 200,
- headers: [{"content-type", "application/activity+json"}],
- body:
- File.read!("test/fixtures/users_mock/masto_featured.json")
- |> String.replace("{{domain}}", "mastodon.social")
- |> String.replace("{{nickname}}", "bastianallgeier")
- }
+ %{url: "https://springfield.social/users/troymcclure"} ->
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }
+
+ %{url: "https://springfield.social/users/hankscorpio"} ->
+ %Tesla.Env{
+ status: 410,
+ body: ""
+ }
+ end)
+ end
+
+ test "when request returns a 403" do
+ params =
+ insert(:note_activity).data
+ |> Map.put("actor", "https://springfield.social/users/bart")
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, {:error, :forbidden}} = ReceiverWorker.perform(oban_job)
+ end
+ test "when request returns a 404" do
+ params =
+ insert(:note_activity).data
+ |> Map.put("actor", "https://springfield.social/users/troymcclure")
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, {:error, :not_found}} = ReceiverWorker.perform(oban_job)
+ end
+
+ test "when request returns a 410" do
+ params =
+ insert(:note_activity).data
+ |> Map.put("actor", "https://springfield.social/users/hankscorpio")
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, {:error, :not_found}} = ReceiverWorker.perform(oban_job)
+ end
+
+ test "when user account is disabled" do
+ user = insert(:user)
+
+ fake_activity = URI.parse(user.ap_id) |> Map.put(:path, "/fake-activity") |> to_string
+
+ params =
+ insert(:note_activity, user: user).data
+ |> Map.put("id", fake_activity)
+
+ {:ok, %User{}} = User.set_activation(user, false)
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, {:user_active, false}} = ReceiverWorker.perform(oban_job)
+ end
+ end
+
+ test "it can validate the signature" do
+ Tesla.Mock.mock(fn
%{url: "https://phpc.social/users/denniskoch"} ->
%Tesla.Env{
status: 200,
@@ -86,136 +168,10 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
|> String.replace("{{domain}}", "phpc.social")
|> String.replace("{{nickname}}", "denniskoch")
}
-
- %{url: "https://mastodon.social/users/bastianallgeier/statuses/112846516276907281"} ->
- %Tesla.Env{
- status: 200,
- headers: [{"content-type", "application/activity+json"}],
- body: File.read!("test/fixtures/receiver_worker_signature_activity.json")
- }
end)
- params = %{
- "@context" => [
- "https://www.w3.org/ns/activitystreams",
- "https://w3id.org/security/v1",
- %{
- "claim" => %{"@id" => "toot:claim", "@type" => "@id"},
- "memorial" => "toot:memorial",
- "atomUri" => "ostatus:atomUri",
- "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
- "blurhash" => "toot:blurhash",
- "ostatus" => "http://ostatus.org#",
- "discoverable" => "toot:discoverable",
- "focalPoint" => %{"@container" => "@list", "@id" => "toot:focalPoint"},
- "votersCount" => "toot:votersCount",
- "Hashtag" => "as:Hashtag",
- "Emoji" => "toot:Emoji",
- "alsoKnownAs" => %{"@id" => "as:alsoKnownAs", "@type" => "@id"},
- "sensitive" => "as:sensitive",
- "movedTo" => %{"@id" => "as:movedTo", "@type" => "@id"},
- "inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
- "conversation" => "ostatus:conversation",
- "Device" => "toot:Device",
- "schema" => "http://schema.org#",
- "toot" => "http://joinmastodon.org/ns#",
- "cipherText" => "toot:cipherText",
- "suspended" => "toot:suspended",
- "messageType" => "toot:messageType",
- "featuredTags" => %{"@id" => "toot:featuredTags", "@type" => "@id"},
- "Curve25519Key" => "toot:Curve25519Key",
- "deviceId" => "toot:deviceId",
- "Ed25519Signature" => "toot:Ed25519Signature",
- "featured" => %{"@id" => "toot:featured", "@type" => "@id"},
- "devices" => %{"@id" => "toot:devices", "@type" => "@id"},
- "value" => "schema:value",
- "PropertyValue" => "schema:PropertyValue",
- "messageFranking" => "toot:messageFranking",
- "publicKeyBase64" => "toot:publicKeyBase64",
- "identityKey" => %{"@id" => "toot:identityKey", "@type" => "@id"},
- "Ed25519Key" => "toot:Ed25519Key",
- "indexable" => "toot:indexable",
- "EncryptedMessage" => "toot:EncryptedMessage",
- "fingerprintKey" => %{"@id" => "toot:fingerprintKey", "@type" => "@id"}
- }
- ],
- "actor" => "https://phpc.social/users/denniskoch",
- "cc" => [
- "https://phpc.social/users/denniskoch/followers",
- "https://mastodon.social/users/bastianallgeier",
- "https://chaos.social/users/distantnative",
- "https://fosstodon.org/users/kev"
- ],
- "id" => "https://phpc.social/users/denniskoch/statuses/112847382711461301/activity",
- "object" => %{
- "atomUri" => "https://phpc.social/users/denniskoch/statuses/112847382711461301",
- "attachment" => [],
- "attributedTo" => "https://phpc.social/users/denniskoch",
- "cc" => [
- "https://phpc.social/users/denniskoch/followers",
- "https://mastodon.social/users/bastianallgeier",
- "https://chaos.social/users/distantnative",
- "https://fosstodon.org/users/kev"
- ],
- "content" =>
- "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.</p>",
- "contentMap" => %{
- "en" =>
- "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.</p>"
- },
- "conversation" =>
- "tag:mastodon.social,2024-07-25:objectId=760068442:objectType=Conversation",
- "id" => "https://phpc.social/users/denniskoch/statuses/112847382711461301",
- "inReplyTo" =>
- "https://mastodon.social/users/bastianallgeier/statuses/112846516276907281",
- "inReplyToAtomUri" =>
- "https://mastodon.social/users/bastianallgeier/statuses/112846516276907281",
- "published" => "2024-07-25T13:33:29Z",
- "replies" => %{
- "first" => %{
- "items" => [],
- "next" =>
- "https://phpc.social/users/denniskoch/statuses/112847382711461301/replies?only_other_accounts=true&page=true",
- "partOf" =>
- "https://phpc.social/users/denniskoch/statuses/112847382711461301/replies",
- "type" => "CollectionPage"
- },
- "id" => "https://phpc.social/users/denniskoch/statuses/112847382711461301/replies",
- "type" => "Collection"
- },
- "sensitive" => false,
- "tag" => [
- %{
- "href" => "https://mastodon.social/users/bastianallgeier",
- "name" => "@bastianallgeier@mastodon.social",
- "type" => "Mention"
- },
- %{
- "href" => "https://chaos.social/users/distantnative",
- "name" => "@distantnative@chaos.social",
- "type" => "Mention"
- },
- %{
- "href" => "https://fosstodon.org/users/kev",
- "name" => "@kev@fosstodon.org",
- "type" => "Mention"
- }
- ],
- "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "type" => "Note",
- "url" => "https://phpc.social/@denniskoch/112847382711461301"
- },
- "published" => "2024-07-25T13:33:29Z",
- "signature" => %{
- "created" => "2024-07-25T13:33:29Z",
- "creator" => "https://phpc.social/users/denniskoch#main-key",
- "signatureValue" =>
- "slz9BKJzd2n1S44wdXGOU+bV/wsskdgAaUpwxj8R16mYOL8+DTpE6VnfSKoZGsBBJT8uG5gnVfVEz1YsTUYtymeUgLMh7cvd8VnJnZPS+oixbmBRVky/Myf91TEgQQE7G4vDmTdB4ii54hZrHcOOYYf5FKPNRSkMXboKA6LMqNtekhbI+JTUJYIB02WBBK6PUyo15f6B1RJ6HGWVgud9NE0y1EZXfrkqUt682p8/9D49ORf7AwjXUJibKic2RbPvhEBj70qUGfBm4vvgdWhSUn1IG46xh+U0+NrTSUED82j1ZVOeua/2k/igkGs8cSBkY35quXTkPz6gbqCCH66CuA==",
- "type" => "RsaSignature2017"
- },
- "to" => ["https://www.w3.org/ns/activitystreams#Public"],
- "type" => "Create"
- }
+ params =
+ File.read!("test/fixtures/receiver_worker_signature_activity.json") |> Jason.decode!()
req_headers = [
["accept-encoding", "gzip"],
@@ -245,4 +201,46 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
assert {:ok, %Pleroma.Activity{}} = ReceiverWorker.perform(oban_job)
end
+
+ test "cancels due to origin containment" do
+ params =
+ insert(:note_activity).data
+ |> Map.put("id", "https://notorigindomain.com/activity")
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, :origin_containment_failed} = ReceiverWorker.perform(oban_job)
+ end
+
+ test "canceled due to deleted object" do
+ params =
+ insert(:announce_activity).data
+ |> Map.put("object", "http://localhost:4001/deleted")
+
+ Tesla.Mock.mock(fn
+ %{url: "http://localhost:4001/deleted"} ->
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }
+ end)
+
+ {:ok, oban_job} =
+ Federator.incoming_ap_doc(%{
+ method: "POST",
+ req_headers: [],
+ request_path: "/inbox",
+ params: params,
+ query_string: ""
+ })
+
+ assert {:cancel, _} = ReceiverWorker.perform(oban_job)
+ end
end