mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2024-12-21 23:56:30 +00:00
Merge branch 'fix/optimistic-inbox' into 'develop'
Fix Optimistic Inbox for failed signatures See merge request pleroma/pleroma!4193
This commit is contained in:
commit
6876761837
12 changed files with 576 additions and 58 deletions
|
@ -2,5 +2,8 @@
|
|||
{"lib/cachex.ex", "Unknown type: Spec.cache/0."},
|
||||
{"lib/pleroma/web/plugs/rate_limiter.ex", "The pattern can never match the type {:commit, _} | {:ignore, _}."},
|
||||
{"lib/pleroma/web/plugs/rate_limiter.ex", "Function get_scale/2 will never be called."},
|
||||
{"lib/pleroma/web/plugs/rate_limiter.ex", "Function initialize_buckets!/1 will never be called."}
|
||||
{"lib/pleroma/web/plugs/rate_limiter.ex", "Function initialize_buckets!/1 will never be called."},
|
||||
{"lib/pleroma/workers/receiver_worker.ex", :call},
|
||||
{"lib/pleroma/workers/receiver_worker.ex", :pattern_match},
|
||||
{"lib/pleroma/workers/receiver_worker.ex", :pattern_match_cov},
|
||||
]
|
||||
|
|
1
changelog.d/optimistic-inbox-sigs.fix
Normal file
1
changelog.d/optimistic-inbox-sigs.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix Optimistic Inbox for failed signatures
|
|
@ -158,8 +158,7 @@ config :pleroma, Pleroma.Uploaders.IPFS, config_impl: Pleroma.UnstubbedConfigMoc
|
|||
config :pleroma, Pleroma.Web.Plugs.HTTPSecurityPlug, config_impl: Pleroma.StaticStubbedConfigMock
|
||||
config :pleroma, Pleroma.Web.Plugs.HTTPSignaturePlug, config_impl: Pleroma.StaticStubbedConfigMock
|
||||
|
||||
config :pleroma, Pleroma.Web.Plugs.HTTPSignaturePlug,
|
||||
http_signatures_impl: Pleroma.StubbedHTTPSignaturesMock
|
||||
config :pleroma, Pleroma.Signature, http_signatures_impl: Pleroma.StubbedHTTPSignaturesMock
|
||||
|
||||
peer_module =
|
||||
if String.to_integer(System.otp_release()) >= 25 do
|
||||
|
|
|
@ -10,6 +10,14 @@ defmodule Pleroma.Signature do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
||||
import Plug.Conn, only: [put_req_header: 3]
|
||||
|
||||
@http_signatures_impl Application.compile_env(
|
||||
:pleroma,
|
||||
[__MODULE__, :http_signatures_impl],
|
||||
HTTPSignatures
|
||||
)
|
||||
|
||||
@known_suffixes ["/publickey", "/main-key"]
|
||||
|
||||
def key_id_to_actor_id(key_id) do
|
||||
|
@ -85,4 +93,48 @@ defmodule Pleroma.Signature do
|
|||
def signed_date(%NaiveDateTime{} = date) do
|
||||
Timex.format!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
|
||||
end
|
||||
|
||||
@spec validate_signature(Plug.Conn.t(), String.t()) :: boolean()
|
||||
def validate_signature(%Plug.Conn{} = conn, request_target) do
|
||||
# Newer drafts for HTTP signatures now use @request-target instead of the
|
||||
# old (request-target). We'll now support both for incoming signatures.
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("(request-target)", request_target)
|
||||
|> put_req_header("@request-target", request_target)
|
||||
|
||||
@http_signatures_impl.validate_conn(conn)
|
||||
end
|
||||
|
||||
@spec validate_signature(Plug.Conn.t()) :: boolean()
|
||||
def validate_signature(%Plug.Conn{} = conn) do
|
||||
# This (request-target) is non-standard, but many implementations do it
|
||||
# this way due to a misinterpretation of
|
||||
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-06
|
||||
# "path" was interpreted as not having the query, though later examples
|
||||
# show that it must be the absolute path + query. This behavior is kept to
|
||||
# make sure most software (Pleroma itself, Mastodon, and probably others)
|
||||
# do not break.
|
||||
request_target = Enum.join([String.downcase(conn.method), conn.request_path], " ")
|
||||
|
||||
# This is the proper way to build the @request-target, as expected by
|
||||
# many HTTP signature libraries, clarified in the following draft:
|
||||
# https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-11.html#section-2.2.6
|
||||
# It is the same as before, but containing the query part as well.
|
||||
proper_target = Enum.join([request_target, "?", conn.query_string], "")
|
||||
|
||||
cond do
|
||||
# Normal, non-standard behavior but expected by Pleroma and more.
|
||||
validate_signature(conn, request_target) ->
|
||||
true
|
||||
|
||||
# Has query string and the previous one failed: let's try the standard.
|
||||
conn.query_string != "" ->
|
||||
validate_signature(conn, proper_target)
|
||||
|
||||
# If there's no query string and signature fails, it's rotten.
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -293,8 +293,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
json(conn, "ok")
|
||||
end
|
||||
|
||||
def inbox(%{assigns: %{valid_signature: false}, req_headers: req_headers} = conn, params) do
|
||||
Federator.incoming_ap_doc(%{req_headers: req_headers, params: params})
|
||||
def inbox(%{assigns: %{valid_signature: false}} = conn, params) do
|
||||
Federator.incoming_ap_doc(%{
|
||||
method: conn.method,
|
||||
req_headers: conn.req_headers,
|
||||
request_path: conn.request_path,
|
||||
params: params,
|
||||
query_string: conn.query_string
|
||||
})
|
||||
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
||||
|
|
|
@ -35,10 +35,12 @@ defmodule Pleroma.Web.Federator do
|
|||
end
|
||||
|
||||
# Client API
|
||||
def incoming_ap_doc(%{params: params, req_headers: req_headers}) do
|
||||
def incoming_ap_doc(%{params: _params, req_headers: _req_headers} = args) do
|
||||
job_args = Enum.into(args, %{}, fn {k, v} -> {Atom.to_string(k), v} end)
|
||||
|
||||
ReceiverWorker.enqueue(
|
||||
"incoming_ap_doc",
|
||||
%{"req_headers" => req_headers, "params" => params, "timeout" => :timer.seconds(20)},
|
||||
Map.put(job_args, "timeout", :timer.seconds(20)),
|
||||
priority: 2
|
||||
)
|
||||
end
|
||||
|
|
|
@ -8,16 +8,12 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
|||
import Plug.Conn
|
||||
import Phoenix.Controller, only: [get_format: 1, text: 2]
|
||||
|
||||
alias Pleroma.Signature
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
require Logger
|
||||
|
||||
@config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
|
||||
@http_signatures_impl Application.compile_env(
|
||||
:pleroma,
|
||||
[__MODULE__, :http_signatures_impl],
|
||||
HTTPSignatures
|
||||
)
|
||||
|
||||
def init(options) do
|
||||
options
|
||||
|
@ -39,48 +35,6 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
|||
end
|
||||
end
|
||||
|
||||
defp validate_signature(conn, request_target) do
|
||||
# Newer drafts for HTTP signatures now use @request-target instead of the
|
||||
# old (request-target). We'll now support both for incoming signatures.
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("(request-target)", request_target)
|
||||
|> put_req_header("@request-target", request_target)
|
||||
|
||||
@http_signatures_impl.validate_conn(conn)
|
||||
end
|
||||
|
||||
defp validate_signature(conn) do
|
||||
# This (request-target) is non-standard, but many implementations do it
|
||||
# this way due to a misinterpretation of
|
||||
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-06
|
||||
# "path" was interpreted as not having the query, though later examples
|
||||
# show that it must be the absolute path + query. This behavior is kept to
|
||||
# make sure most software (Pleroma itself, Mastodon, and probably others)
|
||||
# do not break.
|
||||
request_target = String.downcase("#{conn.method}") <> " #{conn.request_path}"
|
||||
|
||||
# This is the proper way to build the @request-target, as expected by
|
||||
# many HTTP signature libraries, clarified in the following draft:
|
||||
# https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-11.html#section-2.2.6
|
||||
# It is the same as before, but containing the query part as well.
|
||||
proper_target = request_target <> "?#{conn.query_string}"
|
||||
|
||||
cond do
|
||||
# Normal, non-standard behavior but expected by Pleroma and more.
|
||||
validate_signature(conn, request_target) ->
|
||||
true
|
||||
|
||||
# Has query string and the previous one failed: let's try the standard.
|
||||
conn.query_string != "" ->
|
||||
validate_signature(conn, proper_target)
|
||||
|
||||
# If there's no query string and signature fails, it's rotten.
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_assign_valid_signature(conn) do
|
||||
if has_signature_header?(conn) do
|
||||
# we replace the digest header with the one we computed in DigestPlug
|
||||
|
@ -90,7 +44,7 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
|||
conn -> conn
|
||||
end
|
||||
|
||||
assign(conn, :valid_signature, validate_signature(conn))
|
||||
assign(conn, :valid_signature, Signature.validate_signature(conn))
|
||||
else
|
||||
Logger.debug("No signature header!")
|
||||
conn
|
||||
|
|
|
@ -12,17 +12,30 @@ defmodule Pleroma.Workers.ReceiverWorker do
|
|||
@impl Oban.Worker
|
||||
|
||||
def perform(%Job{
|
||||
args: %{"op" => "incoming_ap_doc", "req_headers" => req_headers, "params" => params}
|
||||
args: %{
|
||||
"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 = %{params: params, req_headers: req_headers}
|
||||
conn_data = %Plug.Conn{
|
||||
method: method,
|
||||
params: params,
|
||||
req_headers: req_headers,
|
||||
request_path: request_path,
|
||||
query_string: query_string
|
||||
}
|
||||
|
||||
with {:ok, %User{} = _actor} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]),
|
||||
{:ok, _public_key} <- Signature.refetch_public_key(conn_data),
|
||||
{:signature, true} <- {:signature, HTTPSignatures.validate_conn(conn_data)},
|
||||
{:signature, true} <- {:signature, Signature.validate_signature(conn_data)},
|
||||
{:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
|
||||
{:ok, res}
|
||||
else
|
||||
|
|
117
test/fixtures/bastianallgeier.json
vendored
Normal file
117
test/fixtures/bastianallgeier.json
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"@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 & developer. Creator of Kirby CMS</p>",
|
||||
"tag": [],
|
||||
"type": "Person",
|
||||
"url": "https://mastodon.social/@bastianallgeier"
|
||||
}
|
112
test/fixtures/denniskoch.json
vendored
Normal file
112
test/fixtures/denniskoch.json
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
{
|
||||
"@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": "GitHub",
|
||||
"type": "PropertyValue",
|
||||
"value": "<a href=\"https://github.com/pxlrbt/\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">github.com/pxlrbt/</span><span class=\"invisible\"></span></a>"
|
||||
},
|
||||
{
|
||||
"name": "Discord",
|
||||
"type": "PropertyValue",
|
||||
"value": "pxlrbt#6029"
|
||||
}
|
||||
],
|
||||
"devices": "https://phpc.social/users/denniskoch/collections/devices",
|
||||
"discoverable": true,
|
||||
"endpoints": {
|
||||
"sharedInbox": "https://phpc.social/inbox"
|
||||
},
|
||||
"featured": "https://phpc.social/users/denniskoch/collections/featured",
|
||||
"featuredTags": "https://phpc.social/users/denniskoch/collections/tags",
|
||||
"followers": "https://phpc.social/users/denniskoch/followers",
|
||||
"following": "https://phpc.social/users/denniskoch/following",
|
||||
"icon": {
|
||||
"mediaType": "image/jpeg",
|
||||
"type": "Image",
|
||||
"url": "https://media.phpc.social/accounts/avatars/109/364/097/179/042/485/original/6e770c7b3f5ef72d.jpg"
|
||||
},
|
||||
"id": "https://phpc.social/users/denniskoch",
|
||||
"image": {
|
||||
"mediaType": "image/jpeg",
|
||||
"type": "Image",
|
||||
"url": "https://media.phpc.social/accounts/headers/109/364/097/179/042/485/original/709da24705260c04.jpg"
|
||||
},
|
||||
"inbox": "https://phpc.social/users/denniskoch/inbox",
|
||||
"indexable": true,
|
||||
"manuallyApprovesFollowers": false,
|
||||
"memorial": false,
|
||||
"name": "Dennis Koch",
|
||||
"outbox": "https://phpc.social/users/denniskoch/outbox",
|
||||
"preferredUsername": "denniskoch",
|
||||
"publicKey": {
|
||||
"id": "https://phpc.social/users/denniskoch#main-key",
|
||||
"owner": "https://phpc.social/users/denniskoch",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4dmcSlqLj18gPvuslkmt\nQTniZ8ybO4pgvMvPLYtBuTBUjo49vJ/8Sw6jB5zcKb1haqIdny7Rv/vY3kCdCXcP\nloh1I+jthEgqLT8JpZWGwLGwg9piFhrMGADmt3N8du7HfglzuZ8LlVpnZ8feCw7I\nS2ua/ZCxE47mI45Z3ed2kkFYKWopWWqFn2lan/1OyHrcFKtCvaVjRdvo0UUt2tgl\nvyJI4+zN8FnrCbsMtcbI5nSzfJIrOc4LeaGmLJh+0o2rwoOQZc2487XWbeyfhjsq\nPRBpYN7pfHWQDvzQIN075LHTf9zDFsm6+HqY7Zs5rYxr72rvcX7d9JcP6CasIosY\nqwIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
"published": "2022-11-18T00:00:00Z",
|
||||
"summary": "<p>🧑💻 Full Stack Developer<br />🚀 Laravel, Filament, Livewire, Vue, Inertia<br />🌍 Germany</p>",
|
||||
"tag": [],
|
||||
"type": "Person",
|
||||
"url": "https://phpc.social/@denniskoch"
|
||||
}
|
62
test/fixtures/receiver_worker_signature_activity.json
vendored
Normal file
62
test/fixtures/receiver_worker_signature_activity.json
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
{
|
||||
"atomUri": "ostatus:atomUri",
|
||||
"blurhash": "toot:blurhash",
|
||||
"conversation": "ostatus:conversation",
|
||||
"focalPoint": {
|
||||
"@container": "@list",
|
||||
"@id": "toot:focalPoint"
|
||||
},
|
||||
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||
"ostatus": "http://ostatus.org#",
|
||||
"sensitive": "as:sensitive",
|
||||
"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
|
||||
}
|
||||
],
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"id": "https://chaos.social/users/distantnative/statuses/109336635639931467/replies",
|
||||
"type": "Collection"
|
||||
},
|
||||
"sensitive": false,
|
||||
"summary": null,
|
||||
"tag": [],
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Note",
|
||||
"url": "https://chaos.social/@distantnative/109336635639931467"
|
||||
}
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
|
|||
import Mock
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Web.Federator
|
||||
alias Pleroma.Workers.ReceiverWorker
|
||||
|
||||
test "it does not retry MRF reject" do
|
||||
|
@ -49,4 +50,199 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
|
|||
args: %{"op" => "incoming_ap_doc", "params" => params}
|
||||
})
|
||||
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"}]
|
||||
}
|
||||
|
||||
%{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://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")
|
||||
}
|
||||
|
||||
%{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"
|
||||
}
|
||||
|
||||
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
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue