Split failed-signature inbox retries

Route failed-signature ActivityPub inbox retries through a dedicated worker so legacy and malformed retry jobs fail closed before processing.
This commit is contained in:
Lain Soykaf 2026-05-01 08:43:42 +04:00
commit 7756f491d5
No known key found for this signature in database
7 changed files with 786 additions and 461 deletions

View file

@ -348,7 +348,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end end
def inbox(%{assigns: %{valid_signature: false}} = conn, params) do def inbox(%{assigns: %{valid_signature: false}} = conn, params) do
Federator.incoming_ap_doc(%{ Federator.incoming_failed_signature_ap_doc(%{
method: conn.method, method: conn.method,
req_headers: conn.req_headers, req_headers: conn.req_headers,
request_path: conn.request_path, request_path: conn.request_path,

View file

@ -11,6 +11,7 @@ defmodule Pleroma.Web.Federator do
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Workers.PublisherWorker alias Pleroma.Workers.PublisherWorker
alias Pleroma.Workers.ReceiverWorker alias Pleroma.Workers.ReceiverWorker
alias Pleroma.Workers.SignatureRetryWorker
require Logger require Logger
@ -35,12 +36,21 @@ defmodule Pleroma.Web.Federator do
end end
# Client API # Client API
def incoming_ap_doc(%{params: params, req_headers: req_headers}) do def incoming_failed_signature_ap_doc(%{
ReceiverWorker.new( method: method,
params: params,
req_headers: req_headers,
request_path: request_path,
query_string: query_string
}) do
SignatureRetryWorker.new(
%{ %{
"op" => "incoming_ap_doc", "op" => "incoming_failed_signature_ap_doc",
"method" => method,
"req_headers" => req_headers, "req_headers" => req_headers,
"params" => params, "params" => params,
"request_path" => request_path,
"query_string" => query_string,
"timeout" => :timer.seconds(20) "timeout" => :timer.seconds(20)
}, },
priority: 2 priority: 2

View file

@ -4,55 +4,36 @@
defmodule Pleroma.Workers.ReceiverWorker do defmodule Pleroma.Workers.ReceiverWorker do
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.Signature
alias Pleroma.User
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
alias Pleroma.Web.Plugs.MappedSignatureToIdentityPlug alias Pleroma.Workers.SignatureRetryWorker
use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity] use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity]
@impl true @impl true
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params} = args} = job) do
def perform(%Job{ if signature_retry_job?(args) do
args: %{ perform_signature_retry(job)
"op" => "incoming_ap_doc",
"method" => method,
"params" => params,
"req_headers" => req_headers,
"request_path" => request_path,
"query_string" => query_string
}
}) do
# Oban's serialization converts our tuple headers to lists.
# Revert it for the signature validation.
req_headers = Enum.into(req_headers, [], &List.to_tuple(&1))
conn_data = %Plug.Conn{
assigns: %{valid_signature: true},
method: method,
params: params,
req_headers: req_headers,
request_path: request_path,
query_string: query_string
}
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)},
{:same_actor, true} <- {:same_actor, validate_same_actor(conn_data)},
{:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
unless Instances.reachable?(params["actor"]) do
domain = URI.parse(params["actor"]).host
Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain}))
end
{:ok, res}
else else
e -> process_errors(e) perform_incoming(params)
end end
end end
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do def perform(%Job{args: %{"op" => "incoming_ap_doc"} = args} = job) do
if signature_retry_job?(args) do
perform_signature_retry(job)
else
process_errors(:missing_incoming_ap_doc_params)
end
end
defp perform_signature_retry(%Job{args: args} = job) do
SignatureRetryWorker.perform(%Job{
job
| args: Map.put(args, "op", "incoming_failed_signature_ap_doc")
})
end
defp perform_incoming(params) do
with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
unless Instances.reachable?(params["actor"]) do unless Instances.reachable?(params["actor"]) do
domain = URI.parse(params["actor"]).host domain = URI.parse(params["actor"]).host
@ -65,21 +46,15 @@ defmodule Pleroma.Workers.ReceiverWorker do
end end
end end
defp signature_retry_job?(args) do
Enum.any?(~w(method req_headers request_path query_string), &Map.has_key?(args, &1))
end
@impl true @impl true
def timeout(%_{args: %{"timeout" => timeout}}), do: timeout def timeout(%_{args: %{"timeout" => timeout}}), do: timeout
def timeout(_job), do: :timer.seconds(5) def timeout(_job), do: :timer.seconds(5)
defp validate_same_actor(conn_data) do
case MappedSignatureToIdentityPlug.call(conn_data, []) do
%Plug.Conn{assigns: %{valid_signature: true}} ->
true
_ ->
false
end
end
defp process_errors({:error, {:error, _} = error}), do: process_errors(error) defp process_errors({:error, {:error, _} = error}), do: process_errors(error)
defp process_errors(errors) do defp process_errors(errors) do
@ -103,6 +78,7 @@ defmodule Pleroma.Workers.ReceiverWorker do
{:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed} {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed}
# Unclear if this can be reached # Unclear if this can be reached
{:error, {:side_effects, {:error, :no_object_actor}} = reason} -> {:cancel, reason} {:error, {:side_effects, {:error, :no_object_actor}} = reason} -> {:cancel, reason}
:missing_incoming_ap_doc_params -> {:cancel, :missing_incoming_ap_doc_params}
# Catchall # Catchall
{:error, _} = e -> e {:error, _} = e -> e
e -> {:error, e} e -> {:error, e}

View file

@ -0,0 +1,144 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.SignatureRetryWorker do
alias Pleroma.Instances
alias Pleroma.Signature
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Federator
alias Pleroma.Web.Plugs.MappedSignatureToIdentityPlug
use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity]
@impl true
def perform(%Job{
args: %{
"op" => "incoming_failed_signature_ap_doc",
"method" => method,
"params" => params,
"req_headers" => req_headers,
"request_path" => request_path,
"query_string" => query_string
}
})
when is_binary(method) and is_map(params) and is_list(req_headers) and
is_binary(request_path) and is_binary(query_string) do
with {:ok, req_headers} <- normalize_req_headers(req_headers),
conn_data = %Plug.Conn{
assigns: %{valid_signature: true},
method: method,
params: params,
req_headers: req_headers,
request_path: request_path,
query_string: query_string
},
actor_id = Utils.get_ap_id(params["actor"]),
{:signature_actor, {:ok, signature_actor_id}} <-
{:signature_actor, signature_actor_id(conn_data)},
{:same_actor, true} <- {:same_actor, signature_actor_id == actor_id},
{:ok, %User{}} <- User.get_or_fetch_by_ap_id(actor_id),
{:ok, _public_key} <- Signature.refetch_public_key(conn_data),
{:signature, true} <- {:signature, validate_signature(conn_data)},
{:same_actor, true} <- {:same_actor, validate_same_actor(conn_data)},
{:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
unless Instances.reachable?(params["actor"]) do
domain = URI.parse(params["actor"]).host
Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain}))
end
{:ok, res}
else
e -> process_errors(e)
end
end
def perform(%Job{args: %{"op" => "incoming_failed_signature_ap_doc"}}) do
process_errors(:missing_signature_retry_metadata)
end
def perform(%Job{}), do: process_errors(:missing_signature_retry_metadata)
@impl true
def timeout(%_{args: %{"timeout" => timeout}}), do: timeout
def timeout(_job), do: :timer.seconds(5)
defp normalize_req_headers(req_headers) do
req_headers
|> Enum.reduce_while({:ok, []}, fn
{key, value}, {:ok, acc} when is_binary(key) and is_binary(value) ->
{:cont, {:ok, [{key, value} | acc]}}
[key, value], {:ok, acc} when is_binary(key) and is_binary(value) ->
{:cont, {:ok, [{key, value} | acc]}}
_, _ ->
{:halt, {:error, :invalid_signature_retry_metadata}}
end)
|> case do
{:ok, headers} -> {:ok, Enum.reverse(headers)}
error -> error
end
end
defp validate_same_actor(conn_data) do
case MappedSignatureToIdentityPlug.call(conn_data, []) do
%Plug.Conn{assigns: %{valid_signature: true}} ->
true
_ ->
false
end
end
defp validate_signature(conn_data) do
Signature.validate_signature(conn_data)
rescue
_ -> false
catch
_, _ -> false
end
defp signature_actor_id(conn_data) do
Signature.get_actor_id(conn_data)
rescue
_ -> {:error, :invalid_signature}
catch
_, _ -> {:error, :invalid_signature}
end
defp process_errors({:error, {:error, _} = error}), do: process_errors(error)
defp process_errors(errors) do
case errors do
# 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}
# MRFs will return a reject
{:error, {:reject, _} = reason} -> {:cancel, reason}
# HTTP Sigs
{:signature_actor, {:error, _}} -> {:cancel, :invalid_signature}
{:signature, false} -> {:cancel, :invalid_signature}
{:same_actor, false} -> {:cancel, :actor_signature_mismatch}
# 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}
# Fail closed if the retry cannot reconstruct the original request.
:missing_signature_retry_metadata -> {:cancel, :missing_signature_retry_metadata}
{:error, :invalid_signature_retry_metadata} -> {:cancel, :invalid_signature_retry_metadata}
# Catchall
{:error, _} = e -> e
e -> {:error, e}
end
end
end

View file

@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
alias Pleroma.Workers.ReceiverWorker alias Pleroma.Workers.ReceiverWorker
alias Pleroma.Workers.SignatureRetryWorker
import Pleroma.Factory import Pleroma.Factory
@ -36,6 +37,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
setup do: clear_config([:instance, :federating], true) setup do: clear_config([:instance, :federating], true)
defp expect_signature_retry_from(%User{} = signer) do
signer_json = UserView.render("user.json", %{user: signer}) |> Map.delete("featured")
Tesla.Mock.mock(fn
%{url: url} when url == signer.ap_id ->
%Tesla.Env{
status: 200,
body: Jason.encode!(signer_json),
headers: HttpRequestMock.activitypub_object_headers()
}
env ->
apply(HttpRequestMock, :request, [env])
end)
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> true end)
end
describe "/relay" do describe "/relay" do
setup do: clear_config([:instance, :allow_relay]) setup do: clear_config([:instance, :allow_relay])
@ -727,6 +746,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
end end
test "does not create a forged post after failed signature retry", %{conn: conn} do test "does not create a forged post after failed signature retry", %{conn: conn} do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob") bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
object_id = "https://two.com/objects/inbox-forged-note" object_id = "https://two.com/objects/inbox-forged-note"
@ -750,6 +770,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
} }
} }
expect_signature_retry_from(alice)
conn = conn =
conn conn
|> assign(:valid_signature, false) |> assign(:valid_signature, false)
@ -760,13 +782,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "ok" == json_response(conn, 200) assert "ok" == json_response(conn, 200)
assert [{:cancel, :actor_signature_mismatch}] = assert [{:cancel, :actor_signature_mismatch}] =
ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) ObanHelpers.perform(all_enqueued(worker: SignatureRetryWorker))
refute Activity.get_by_ap_id(data["id"]) refute Activity.get_by_ap_id(data["id"])
refute Object.get_by_ap_id(object_id) refute Object.get_by_ap_id(object_id)
end end
test "does not create a forged like after failed signature retry", %{conn: conn} do test "does not create a forged like after failed signature retry", %{conn: conn} do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob") bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
note = insert(:note) note = insert(:note)
@ -779,6 +802,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
"object" => note.data["id"] "object" => note.data["id"]
} }
expect_signature_retry_from(alice)
conn = conn =
conn conn
|> assign(:valid_signature, false) |> assign(:valid_signature, false)
@ -789,7 +814,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "ok" == json_response(conn, 200) assert "ok" == json_response(conn, 200)
assert [{:cancel, :actor_signature_mismatch}] = assert [{:cancel, :actor_signature_mismatch}] =
ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) ObanHelpers.perform(all_enqueued(worker: SignatureRetryWorker))
refute Activity.get_by_ap_id(data["id"]) refute Activity.get_by_ap_id(data["id"])
end end
@ -820,7 +845,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
} }
} }
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> true end) expect_signature_retry_from(alice)
conn = conn =
conn conn
@ -837,7 +862,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "ok" == json_response(conn, 200) assert "ok" == json_response(conn, 200)
assert [{:cancel, :actor_signature_mismatch}] = assert [{:cancel, :actor_signature_mismatch}] =
ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) ObanHelpers.perform(all_enqueued(worker: SignatureRetryWorker))
refute Activity.get_by_ap_id(data["id"]) refute Activity.get_by_ap_id(data["id"])
refute Object.get_by_ap_id(object_id) refute Object.get_by_ap_id(object_id)
@ -858,7 +883,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
"object" => note.data["id"] "object" => note.data["id"]
} }
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> true end) expect_signature_retry_from(alice)
conn = conn =
conn conn
@ -875,7 +900,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert "ok" == json_response(conn, 200) assert "ok" == json_response(conn, 200)
assert [{:cancel, :actor_signature_mismatch}] = assert [{:cancel, :actor_signature_mismatch}] =
ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) ObanHelpers.perform(all_enqueued(worker: SignatureRetryWorker))
refute Activity.get_by_ap_id(data["id"]) refute Activity.get_by_ap_id(data["id"])
end end

View file

@ -11,11 +11,9 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.Federator
alias Pleroma.Workers.ReceiverWorker alias Pleroma.Workers.ReceiverWorker
defp mismatched_signature_headers do defp signature_headers_for(%User{} = signer) do
[ [
{"host", "local.test"}, {"host", "local.test"},
{"date", "Thu, 25 Jul 2024 13:33:31 GMT"}, {"date", "Thu, 25 Jul 2024 13:33:31 GMT"},
@ -23,39 +21,15 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
{"content-type", "application/activity+json"}, {"content-type", "application/activity+json"},
{ {
"signature", "signature",
"keyId=\"https://one.com/users/alice#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\"" "keyId=\"#{signer.ap_id}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
} }
] ]
end end
defp expect_signature_from(%User{} = signer) do defp perform_incoming(params) do
signer_json = UserView.render("user.json", %{user: signer}) |> Map.delete("featured") ReceiverWorker.perform(%Oban.Job{
args: %{"op" => "incoming_ap_doc", "params" => params}
Tesla.Mock.mock(fn })
%{url: url} when url == signer.ap_id ->
%Tesla.Env{
status: 200,
body: Jason.encode!(signer_json),
headers: HttpRequestMock.activitypub_object_headers()
}
end)
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> true end)
end
defp assert_mismatched_signature_cancelled(params, signer) do
expect_signature_from(signer)
assert {:ok, oban_job} =
Federator.incoming_ap_doc(%{
method: "POST",
req_headers: mismatched_signature_headers(),
request_path: "/inbox",
params: params,
query_string: ""
})
assert {:cancel, :actor_signature_mismatch} = ReceiverWorker.perform(oban_job)
end end
test "it does not retry MRF reject" do test "it does not retry MRF reject" do
@ -125,16 +99,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
insert(:note_activity).data insert(:note_activity).data
|> Map.put("actor", "https://springfield.social/users/bart") |> Map.put("actor", "https://springfield.social/users/bart")
{:ok, oban_job} = assert {:cancel, {:error, :forbidden}} = perform_incoming(params)
Federator.incoming_ap_doc(%{
method: "POST",
req_headers: [],
request_path: "/inbox",
params: params,
query_string: ""
})
assert {:cancel, {:error, :forbidden}} = ReceiverWorker.perform(oban_job)
end end
test "when request returns a 404" do test "when request returns a 404" do
@ -142,16 +107,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
insert(:note_activity).data insert(:note_activity).data
|> Map.put("actor", "https://springfield.social/users/troymcclure") |> Map.put("actor", "https://springfield.social/users/troymcclure")
{:ok, oban_job} = assert {:cancel, {:error, :not_found}} = perform_incoming(params)
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 end
test "when request returns a 410" do test "when request returns a 410" do
@ -159,16 +115,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
insert(:note_activity).data insert(:note_activity).data
|> Map.put("actor", "https://springfield.social/users/hankscorpio") |> Map.put("actor", "https://springfield.social/users/hankscorpio")
{:ok, oban_job} = assert {:cancel, {:error, :not_found}} = perform_incoming(params)
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 end
test "when user account is disabled" do test "when user account is disabled" do
@ -182,86 +129,16 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
{:ok, %User{}} = User.set_activation(user, false) {:ok, %User{}} = User.set_activation(user, false)
{:ok, oban_job} = assert {:cancel, {:user_active, false}} = perform_incoming(params)
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
end end
test "it can validate the signature" do
Tesla.Mock.mock(fn
%{url: "https://phpc.social/users/denniskoch"} ->
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/denniskoch.json"),
headers: [{"content-type", "application/activity+json"}]
}
%{url: "https://phpc.social/users/denniskoch/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}}", "phpc.social")
|> String.replace("{{nickname}}", "denniskoch")
}
end)
params =
File.read!("test/fixtures/receiver_worker_signature_activity.json") |> Jason.decode!()
req_headers = [
["accept-encoding", "gzip"],
["content-length", "5184"],
["content-type", "application/activity+json"],
["date", "Thu, 25 Jul 2024 13:33:31 GMT"],
["digest", "SHA-256=ouge/6HP2/QryG6F3JNtZ6vzs/hSwMk67xdxe87eH7A="],
["host", "bikeshed.party"],
[
"signature",
"keyId=\"https://mastodon.social/users/bastianallgeier#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"ymE3vn5Iw50N6ukSp8oIuXJB5SBjGAGjBasdTDvn+ahZIzq2SIJfmVCsIIzyqIROnhWyQoTbavTclVojEqdaeOx+Ejz2wBnRBmhz5oemJLk4RnnCH0lwMWyzeY98YAvxi9Rq57Gojuv/1lBqyGa+rDzynyJpAMyFk17XIZpjMKuTNMCbjMDy76ILHqArykAIL/v1zxkgwxY/+ELzxqMpNqtZ+kQ29znNMUBB3eVZ/mNAHAz6o33Y9VKxM2jw+08vtuIZOusXyiHbRiaj2g5HtN2WBUw1MzzfRfHF2/yy7rcipobeoyk5RvP5SyHV3WrIeZ3iyoNfmv33y8fxllF0EA==\""
],
[
"user-agent",
"http.rb/5.2.0 (Mastodon/4.3.0-nightly.2024-07-25; +https://mastodon.social/)"
]
]
{:ok, oban_job} =
Federator.incoming_ap_doc(%{
method: "POST",
req_headers: req_headers,
request_path: "/inbox",
params: params,
query_string: ""
})
assert {:ok, %Pleroma.Activity{}} = ReceiverWorker.perform(oban_job)
end
test "cancels due to origin containment" do test "cancels due to origin containment" do
params = params =
insert(:note_activity).data insert(:note_activity).data
|> Map.put("id", "https://notorigindomain.com/activity") |> Map.put("id", "https://notorigindomain.com/activity")
{:ok, oban_job} = assert {:cancel, :origin_containment_failed} = perform_incoming(params)
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 end
test "canceled due to deleted object" do test "canceled due to deleted object" do
@ -277,16 +154,98 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
} }
end) end)
{:ok, oban_job} = assert {:cancel, _} = perform_incoming(params)
Federator.incoming_ap_doc(%{ end
method: "POST",
req_headers: [],
request_path: "/inbox",
params: params,
query_string: ""
})
assert {:cancel, _} = ReceiverWorker.perform(oban_job) test "delegates legacy failed-signature metadata jobs instead of processing them as trusted" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
object_id = "https://two.com/objects/legacy-forged-note"
create = %{
"type" => "Create",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/legacy-forged-create",
"context" => "https://two.com/contexts/legacy-forged-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => object_id,
"actor" => bob.ap_id,
"attributedTo" => bob.ap_id,
"context" => "https://two.com/contexts/legacy-forged-create",
"content" => "forged post",
"published" => "2024-07-25T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
assert {:cancel, :actor_signature_mismatch} =
ReceiverWorker.perform(%Oban.Job{
args: %{
"op" => "incoming_ap_doc",
"method" => "POST",
"params" => create,
"req_headers" => signature_headers_for(alice),
"request_path" => "/inbox",
"query_string" => ""
}
})
refute Pleroma.Activity.get_by_ap_id(create["id"])
refute Pleroma.Object.get_by_ap_id(object_id)
end
test "fails closed for the old persisted failed-signature job shape" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
object_id = "https://two.com/objects/old-shape-forged-note"
create = %{
"type" => "Create",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/old-shape-forged-create",
"context" => "https://two.com/contexts/old-shape-forged-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => object_id,
"actor" => bob.ap_id,
"attributedTo" => bob.ap_id,
"context" => "https://two.com/contexts/old-shape-forged-create",
"content" => "forged post",
"published" => "2024-07-25T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
assert {:cancel, :missing_signature_retry_metadata} =
ReceiverWorker.perform(%Oban.Job{
args: %{
"op" => "incoming_ap_doc",
"params" => create,
"req_headers" => signature_headers_for(alice),
"timeout" => 20_000
}
})
refute Pleroma.Activity.get_by_ap_id(create["id"])
refute Pleroma.Object.get_by_ap_id(object_id)
end
test "fails closed for malformed legacy metadata jobs without params" do
assert {:cancel, :missing_signature_retry_metadata} =
ReceiverWorker.perform(%Oban.Job{
args: %{
"op" => "incoming_ap_doc",
"req_headers" => [],
"timeout" => 20_000
}
})
end end
describe "Server reachability:" do describe "Server reachability:" do
@ -346,262 +305,4 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
end end
end end
end end
test "cancels when signature actor does not match payload actor" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
note =
insert(:note,
user: bob,
object_local: false,
data: %{"id" => "https://two.com/objects/malicious-update-note"}
)
update = %{
"type" => "Update",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/malicious-update",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data
}
req_headers = [
["host", "local.test"],
["date", "Thu, 25 Jul 2024 13:33:31 GMT"],
["digest", "SHA-256=fake-digest"],
["content-type", "application/activity+json"],
[
"signature",
"keyId=\"https://one.com/users/alice#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
]
]
oban_job = %Oban.Job{
args: %{
"op" => "incoming_ap_doc",
"method" => "POST",
"params" => update,
"req_headers" => req_headers,
"request_path" => "/inbox",
"query_string" => ""
}
}
expect_signature_from(alice)
assert {:cancel, :actor_signature_mismatch} = ReceiverWorker.perform(oban_job)
end
test "Federator preserves request metadata needed for ReceiverWorker signature checks" do
params = insert(:note_activity).data
req_headers = [
{"host", "local.test"},
{"signature", "keyId=\"https://one.com/users/alice#main-key\""}
]
assert {:ok, oban_job} =
Federator.incoming_ap_doc(%{
method: "POST",
req_headers: req_headers,
request_path: "/inbox",
params: params,
query_string: "foo=bar"
})
assert %{
"method" => "POST",
"req_headers" => ^req_headers,
"request_path" => "/inbox",
"params" => ^params,
"query_string" => "foo=bar"
} = oban_job.args
end
test "cancels signature actor mismatch through Federator-created jobs" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
note =
insert(:note,
user: bob,
object_local: false,
data: %{"id" => "https://two.com/objects/federator-malicious-note"}
)
update = %{
"type" => "Update",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/federator-malicious-update",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data
}
assert_mismatched_signature_cancelled(update, alice)
end
test "cancels signature actor mismatch before processing a forged Create" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
create = %{
"type" => "Create",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/forged-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => "https://two.com/objects/forged-note",
"actor" => bob.ap_id,
"attributedTo" => bob.ap_id,
"content" => "forged post",
"published" => "2024-07-25T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
assert_mismatched_signature_cancelled(create, alice)
end
test "cancels signature actor mismatch before actually creating a forged post" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
object_id = "https://two.com/objects/actually-forged-note"
create = %{
"type" => "Create",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/actually-forged-create",
"context" => "https://two.com/contexts/actually-forged-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => object_id,
"actor" => bob.ap_id,
"attributedTo" => bob.ap_id,
"context" => "https://two.com/contexts/actually-forged-create",
"content" => "forged post",
"published" => "2024-07-25T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
expect_signature_from(alice)
assert {:ok, oban_job} =
Federator.incoming_ap_doc(%{
method: "POST",
req_headers: mismatched_signature_headers(),
request_path: "/inbox",
params: create,
query_string: ""
})
assert {:cancel, :actor_signature_mismatch} = ReceiverWorker.perform(oban_job)
refute Pleroma.Object.get_by_ap_id(object_id)
end
test "cancels signature actor mismatch before processing a forged Like" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
note = insert(:note)
like = %{
"type" => "Like",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/forged-like",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data["id"]
}
assert_mismatched_signature_cancelled(like, alice)
end
test "cancels signature actor mismatch before actually creating a forged Like" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
note = insert(:note)
like = %{
"type" => "Like",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/actually-forged-like",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data["id"]
}
expect_signature_from(alice)
assert {:ok, oban_job} =
Federator.incoming_ap_doc(%{
method: "POST",
req_headers: mismatched_signature_headers(),
request_path: "/inbox",
params: like,
query_string: ""
})
assert {:cancel, :actor_signature_mismatch} = ReceiverWorker.perform(oban_job)
refute Pleroma.Activity.get_by_ap_id(like["id"])
end
test "cancels signature actor mismatch before processing a forged Announce" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
note = insert(:note)
announce = %{
"type" => "Announce",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/forged-announce",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data["id"]
}
assert_mismatched_signature_cancelled(announce, alice)
end
test "cancels signature actor mismatch before processing a forged Follow" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
followed = insert(:user)
follow = %{
"type" => "Follow",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/forged-follow",
"to" => [followed.ap_id],
"cc" => [],
"object" => followed.ap_id
}
assert_mismatched_signature_cancelled(follow, alice)
end
test "cancels signature actor mismatch before processing a forged Undo" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
undo = %{
"type" => "Undo",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/forged-undo",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => "https://two.com/activities/existing-bob-activity"
}
assert_mismatched_signature_cancelled(undo, alice)
end
end end

View file

@ -0,0 +1,469 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.SignatureRetryWorkerTest do
use Pleroma.DataCase, async: false
use Oban.Testing, repo: Pleroma.Repo
import Pleroma.Factory
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Signature
alias Pleroma.User
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.Federator
alias Pleroma.Workers.SignatureRetryWorker
defp signature_headers_for(%User{} = signer) do
[
{"host", "local.test"},
{"date", "Thu, 25 Jul 2024 13:33:31 GMT"},
{"digest", "SHA-256=fake-digest"},
{"content-type", "application/activity+json"},
{
"signature",
"keyId=\"#{signer.ap_id}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
}
]
end
defp stub_actor_fetch(%User{} = signer) do
signer_json = UserView.render("user.json", %{user: signer}) |> Map.delete("featured")
Tesla.Mock.mock(fn
%{url: url} when url == signer.ap_id ->
%Tesla.Env{
status: 200,
body: Jason.encode!(signer_json),
headers: HttpRequestMock.activitypub_object_headers()
}
end)
end
defp expect_signature_from(%User{} = signer) do
stub_actor_fetch(signer)
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> true end)
end
defp enqueue_failed_signature(params, signer) do
Federator.incoming_failed_signature_ap_doc(%{
method: "POST",
req_headers: signature_headers_for(signer),
request_path: "/inbox",
params: params,
query_string: ""
})
end
defp failed_signature_job(params, req_headers, opts \\ []) do
%Oban.Job{
args: %{
"op" => "incoming_failed_signature_ap_doc",
"method" => Keyword.get(opts, :method, "POST"),
"req_headers" => req_headers,
"request_path" => Keyword.get(opts, :request_path, "/inbox"),
"params" => params,
"query_string" => Keyword.get(opts, :query_string, "")
}
}
end
defp assert_mismatched_signature_cancelled(params, signer) do
assert {:ok, oban_job} = enqueue_failed_signature(params, signer)
assert {:cancel, :actor_signature_mismatch} = SignatureRetryWorker.perform(oban_job)
end
test "Federator preserves request metadata for failed-signature retry jobs" do
params = insert(:note_activity).data
req_headers = [
{"host", "local.test"},
{"signature", "keyId=\"https://one.com/users/alice#main-key\""}
]
assert {:ok, oban_job} =
Federator.incoming_failed_signature_ap_doc(%{
method: "POST",
req_headers: req_headers,
request_path: "/inbox",
params: params,
query_string: "foo=bar"
})
assert oban_job.worker == "Pleroma.Workers.SignatureRetryWorker"
assert %{
"op" => "incoming_failed_signature_ap_doc",
"method" => "POST",
"req_headers" => ^req_headers,
"request_path" => "/inbox",
"params" => ^params,
"query_string" => "foo=bar"
} = oban_job.args
end
test "cancels retry jobs without request metadata" do
params = insert(:note_activity).data
assert {:cancel, :missing_signature_retry_metadata} =
SignatureRetryWorker.perform(%Oban.Job{
args: %{"op" => "incoming_failed_signature_ap_doc", "params" => params}
})
end
test "cancels retry jobs with malformed serialized request headers" do
params = insert(:note_activity).data
assert {:cancel, :invalid_signature_retry_metadata} =
SignatureRetryWorker.perform(failed_signature_job(params, [["signature"]]))
end
test "cancels retry jobs without a signature header" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
params = insert(:note_activity, user: alice).data
assert {:cancel, :invalid_signature} =
SignatureRetryWorker.perform(failed_signature_job(params, [{"host", "local.test"}]))
end
test "cancels missing signature before fetching an unavailable payload actor" do
params =
insert(:note_activity).data
|> Map.put("actor", "https://unavailable.example/users/bob")
assert {:cancel, :invalid_signature} =
SignatureRetryWorker.perform(failed_signature_job(params, [{"host", "local.test"}]))
end
test "cancels signer mismatch before fetching an unavailable payload actor" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
params =
insert(:note_activity).data
|> Map.put("actor", "https://unavailable.example/users/bob")
assert {:cancel, :actor_signature_mismatch} =
SignatureRetryWorker.perform(
failed_signature_job(params, signature_headers_for(alice))
)
end
test "cancels retry jobs with a signature header without keyId" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
params = insert(:note_activity, user: alice).data
req_headers = [{"signature", "algorithm=\"rsa-sha256\",signature=\"fake-signature\""}]
assert {:cancel, :invalid_signature} =
SignatureRetryWorker.perform(failed_signature_job(params, req_headers))
end
test "cancels retry jobs with an unparsable signature keyId" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
params = insert(:note_activity, user: alice).data
req_headers = [{"signature", "keyId=\"not an activitypub id\",signature=\"fake-signature\""}]
assert {:cancel, :invalid_signature} =
SignatureRetryWorker.perform(failed_signature_job(params, req_headers))
end
test "cancels when the refetched key still cannot validate the signature" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
create = %{
"type" => "Create",
"actor" => alice.ap_id,
"id" => "https://one.com/activities/invalid-signature-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => "https://one.com/objects/invalid-signature-note",
"actor" => alice.ap_id,
"attributedTo" => alice.ap_id,
"content" => "forged post",
"published" => "2024-07-25T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
stub_actor_fetch(alice)
assert {:ok, oban_job} = enqueue_failed_signature(create, alice)
assert {:cancel, :invalid_signature} = SignatureRetryWorker.perform(oban_job)
refute Activity.get_by_ap_id(create["id"])
end
test "processes the activity after refetching a valid matching signature" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
create = %{
"type" => "Create",
"actor" => alice.ap_id,
"id" => "https://one.com/activities/valid-signature-create",
"context" => "https://one.com/contexts/valid-signature-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => "https://one.com/objects/valid-signature-note",
"actor" => alice.ap_id,
"attributedTo" => alice.ap_id,
"context" => "https://one.com/contexts/valid-signature-create",
"content" => "valid post",
"published" => "2024-07-25T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
expect_signature_from(alice)
assert {:ok, oban_job} = enqueue_failed_signature(create, alice)
assert {:ok, %Activity{}} = SignatureRetryWorker.perform(oban_job)
assert Activity.get_by_ap_id(create["id"])
end
test "processes the activity when a real signature validates with a query string" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
create = %{
"type" => "Create",
"actor" => alice.ap_id,
"id" => "https://one.com/activities/valid-query-signature-create",
"context" => "https://one.com/contexts/valid-query-signature-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => "https://one.com/objects/valid-query-signature-note",
"actor" => alice.ap_id,
"attributedTo" => alice.ap_id,
"context" => "https://one.com/contexts/valid-query-signature-create",
"content" => "valid signed post",
"published" => "2024-07-25T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
stub_actor_fetch(alice)
date = "Thu, 25 Jul 2024 13:33:31 GMT"
digest = "SHA-256=fake-digest"
signature =
Signature.sign(alice, %{
"(request-target)" => "post /inbox?foo=bar",
"content-type" => "application/activity+json",
date: date,
digest: digest,
host: "local.test"
})
req_headers = [
["host", "local.test"],
["date", date],
["digest", digest],
["content-type", "application/activity+json"],
["signature", signature]
]
assert {:ok, %Activity{}} =
SignatureRetryWorker.perform(
failed_signature_job(create, req_headers, query_string: "foo=bar")
)
assert Activity.get_by_ap_id(create["id"])
end
test "cancels when signature actor does not match payload actor" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
note =
insert(:note,
user: bob,
object_local: false,
data: %{"id" => "https://two.com/objects/malicious-update-note"}
)
update = %{
"type" => "Update",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/malicious-update",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data
}
assert_mismatched_signature_cancelled(update, alice)
end
test "cancels signature actor mismatch through Federator-created jobs" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
note =
insert(:note,
user: bob,
object_local: false,
data: %{"id" => "https://two.com/objects/federator-malicious-note"}
)
update = %{
"type" => "Update",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/federator-malicious-update",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data
}
assert_mismatched_signature_cancelled(update, alice)
end
test "cancels signature actor mismatch before processing a forged Create" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
create = %{
"type" => "Create",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/forged-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => "https://two.com/objects/forged-note",
"actor" => bob.ap_id,
"attributedTo" => bob.ap_id,
"content" => "forged post",
"published" => "2024-07-25T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
assert_mismatched_signature_cancelled(create, alice)
end
test "cancels signature actor mismatch before actually creating a forged post" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
object_id = "https://two.com/objects/actually-forged-note"
create = %{
"type" => "Create",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/actually-forged-create",
"context" => "https://two.com/contexts/actually-forged-create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => %{
"type" => "Note",
"id" => object_id,
"actor" => bob.ap_id,
"attributedTo" => bob.ap_id,
"context" => "https://two.com/contexts/actually-forged-create",
"content" => "forged post",
"published" => "2024-07-25T13:33:31Z",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => []
}
}
assert_mismatched_signature_cancelled(create, alice)
refute Object.get_by_ap_id(object_id)
end
test "cancels signature actor mismatch before processing a forged Like" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
note = insert(:note)
like = %{
"type" => "Like",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/forged-like",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data["id"]
}
assert_mismatched_signature_cancelled(like, alice)
end
test "cancels signature actor mismatch before actually creating a forged Like" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
note = insert(:note)
like = %{
"type" => "Like",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/actually-forged-like",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data["id"]
}
assert_mismatched_signature_cancelled(like, alice)
refute Activity.get_by_ap_id(like["id"])
end
test "cancels signature actor mismatch before processing a forged Announce" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
note = insert(:note)
announce = %{
"type" => "Announce",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/forged-announce",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => note.data["id"]
}
assert_mismatched_signature_cancelled(announce, alice)
end
test "cancels signature actor mismatch before processing a forged Follow" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
followed = insert(:user)
follow = %{
"type" => "Follow",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/forged-follow",
"to" => [followed.ap_id],
"cc" => [],
"object" => followed.ap_id
}
assert_mismatched_signature_cancelled(follow, alice)
end
test "cancels signature actor mismatch before processing a forged Undo" do
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
undo = %{
"type" => "Undo",
"actor" => bob.ap_id,
"id" => "https://two.com/activities/forged-undo",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"object" => "https://two.com/activities/existing-bob-activity"
}
assert_mismatched_signature_cancelled(undo, alice)
end
end