Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
31 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/workers/signature_retry_worker.ex b/lib/pleroma/workers/signature_retry_worker.ex
index 56673a514..2c4c097dd 100644
--- a/lib/pleroma/workers/signature_retry_worker.ex
+++ b/lib/pleroma/workers/signature_retry_worker.ex
@@ -1,144 +1,254 @@
# 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
+ require Logger
+
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
+ case normalize_req_headers(req_headers) do
+ {:ok, 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
+ }
- {:ok, res}
- else
- e -> process_errors(e)
+ signature_actor_result = signature_actor_id(conn_data)
+
+ with actor_id = Utils.get_ap_id(params["actor"]),
+ {:signature_actor, {:ok, signature_actor_id}} <-
+ {:signature_actor, signature_actor_result},
+ {: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, retry_log_context(params, request_path, signature_actor_result))
+ end
+
+ e ->
+ process_errors(e, retry_log_context(params, request_path, nil))
end
end
- def perform(%Job{args: %{"op" => "incoming_failed_signature_ap_doc"}}) do
- process_errors(:missing_signature_retry_metadata)
+ def perform(%Job{args: %{"op" => "incoming_failed_signature_ap_doc"} = args}) do
+ process_errors(
+ :missing_signature_retry_metadata,
+ retry_log_context(Map.get(args, "params"), Map.get(args, "request_path"), nil)
+ )
+ end
+
+ def perform(%Job{args: args}) when is_map(args) do
+ process_errors(
+ :missing_signature_retry_metadata,
+ retry_log_context(Map.get(args, "params"), Map.get(args, "request_path"), nil)
+ )
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
+ defp process_errors(errors, context \\ %{})
+
+ defp process_errors({:error, {:error, _} = error}, context), do: process_errors(error, context)
+
+ defp process_errors(errors, context) do
+ result =
+ 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
+
+ log_signature_retry_rejection(result, context)
+ result
end
+
+ defp retry_log_context(params, request_path, signature_actor_result) when is_map(params) do
+ signature_actor =
+ case signature_actor_result do
+ {:ok, actor} when is_binary(actor) -> actor
+ actor when is_binary(actor) -> actor
+ _ -> nil
+ end
+
+ %{
+ activity_id: params["id"],
+ payload_actor: Utils.get_ap_id(params["actor"]),
+ request_path: request_path,
+ signature_actor: signature_actor,
+ type: params["type"]
+ }
+ end
+
+ defp retry_log_context(_params, request_path, signature_actor_result) do
+ signature_actor =
+ case signature_actor_result do
+ {:ok, actor} when is_binary(actor) -> actor
+ actor when is_binary(actor) -> actor
+ _ -> nil
+ end
+
+ %{
+ activity_id: nil,
+ payload_actor: nil,
+ request_path: request_path,
+ signature_actor: signature_actor,
+ type: nil
+ }
+ end
+
+ defp log_signature_retry_rejection({:cancel, reason}, context)
+ when reason in [
+ :actor_signature_mismatch,
+ :invalid_signature,
+ :invalid_signature_retry_metadata,
+ :missing_signature_retry_metadata,
+ :origin_containment_failed
+ ] do
+ Logger.warning(
+ "Failed-signature inbox retry rejected " <>
+ "reason=#{inspect(reason)} " <>
+ "payload_actor=#{inspect(context[:payload_actor])} " <>
+ "signature_actor=#{inspect(context[:signature_actor])} " <>
+ "activity_id=#{inspect(context[:activity_id])} " <>
+ "type=#{inspect(context[:type])} " <>
+ "request_path=#{inspect(context[:request_path])}"
+ )
+ end
+
+ defp log_signature_retry_rejection(_result, _context), do: :ok
end
diff --git a/test/pleroma/workers/signature_retry_worker_test.exs b/test/pleroma/workers/signature_retry_worker_test.exs
index 02706ebad..f4ec0e2e3 100644
--- a/test/pleroma/workers/signature_retry_worker_test.exs
+++ b/test/pleroma/workers/signature_retry_worker_test.exs
@@ -1,469 +1,549 @@
# 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 ExUnit.CaptureLog
import Pleroma.Factory
+ @moduletag capture_log: true
+
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)
+ capture_log([level: :warning], fn ->
+ assert {:cancel, :actor_signature_mismatch} = SignatureRetryWorker.perform(oban_job)
+ end)
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}
- })
+ log =
+ capture_log([level: :warning], fn ->
+ assert {:cancel, :missing_signature_retry_metadata} =
+ SignatureRetryWorker.perform(%Oban.Job{
+ args: %{"op" => "incoming_failed_signature_ap_doc", "params" => params}
+ })
+ end)
+
+ assert log =~ "Failed-signature inbox retry rejected"
+ assert log =~ "reason=:missing_signature_retry_metadata"
+ assert log =~ "payload_actor=#{inspect(params["actor"])}"
+ assert log =~ "activity_id=#{inspect(params["id"])}"
+ assert log =~ "type=#{inspect(params["type"])}"
+ assert log =~ "request_path=nil"
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"]]))
+ log =
+ capture_log([level: :warning], fn ->
+ assert {:cancel, :invalid_signature_retry_metadata} =
+ SignatureRetryWorker.perform(failed_signature_job(params, [["signature"]]))
+ end)
+
+ assert log =~ "Failed-signature inbox retry rejected"
+ assert log =~ "reason=:invalid_signature_retry_metadata"
+ assert log =~ "signature_actor=nil"
+ assert log =~ "request_path=\"/inbox\""
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"}]))
+ log =
+ capture_log([level: :warning], fn ->
+ assert {:cancel, :invalid_signature} =
+ SignatureRetryWorker.perform(
+ failed_signature_job(params, [{"host", "local.test"}])
+ )
+ end)
+
+ assert log =~ "Failed-signature inbox retry rejected"
+ assert log =~ "reason=:invalid_signature"
+ assert log =~ "payload_actor=#{inspect(params["actor"])}"
+ assert log =~ "signature_actor=nil"
+ assert log =~ "request_path=\"/inbox\""
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)
+
+ log =
+ capture_log([level: :warning], fn ->
+ assert {:cancel, :invalid_signature} = SignatureRetryWorker.perform(oban_job)
+ end)
+
+ assert log =~ "Failed-signature inbox retry rejected"
+ assert log =~ "reason=:invalid_signature"
+ assert log =~ "payload_actor=\"https://one.com/users/alice\""
+ assert log =~ "signature_actor=\"https://one.com/users/alice\""
+ assert log =~ "activity_id=\"https://one.com/activities/invalid-signature-create\""
+ assert log =~ "type=\"Create\""
+ assert log =~ "request_path=\"/inbox\""
+
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 "logs signature actor mismatch retry rejections" 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/logged-forged-create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [],
+ "object" => %{
+ "type" => "Note",
+ "id" => "https://two.com/objects/logged-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" => []
+ }
+ }
+
+ log = assert_mismatched_signature_cancelled(create, alice)
+
+ assert log =~ "Failed-signature inbox retry rejected"
+ assert log =~ "reason=:actor_signature_mismatch"
+ assert log =~ "payload_actor=\"https://two.com/users/bob\""
+ assert log =~ "signature_actor=\"https://one.com/users/alice\""
+ assert log =~ "activity_id=\"https://two.com/activities/logged-forged-create\""
+ assert log =~ "type=\"Create\""
+ assert log =~ "request_path=\"/inbox\""
+ 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

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 31, 8:16 AM (9 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1801184
Default Alt Text
(31 KB)

Event Timeline