Merge branch 'bugs' into 'main'

Federation fixes

See merge request framasoft/mobilizon!1114
This commit is contained in:
Thomas Citharel 2021-11-20 17:22:29 +00:00
commit 07d7f908c9
11 changed files with 61 additions and 255 deletions

View file

@ -45,8 +45,12 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
{recipients, followers} = convert_followers_in_recipients(recipients)
Logger.debug("Found the following followers: #{inspect(Enum.map(followers, & &1.url))}")
{recipients, members} = convert_members_in_recipients(recipients)
Logger.debug("Found the following followers: #{inspect(Enum.map(members, & &1.url))}")
remote_inboxes =
(remote_actors(recipients) ++ followers ++ members)
|> Enum.map(fn actor -> actor.shared_inbox_url || actor.inbox_url end)

View file

@ -192,6 +192,8 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
_attributed_to
)
when is_map(object) do
Logger.debug("Maybe relay if group activity (object is map)")
Logger.debug(inspect(object))
do_maybe_relay_if_group_activity(object, object["attributedTo"])
end
@ -201,10 +203,12 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
%Actor{url: attributed_to_url}
)
when is_binary(object) and is_binary(attributed_to_url) do
Logger.debug("Maybe relay if group activity (object is binary)")
do_maybe_relay_if_group_activity(object, attributed_to_url)
end
def maybe_relay_if_group_activity(_activity, _attributedTo) do
Logger.debug("Will not replay : not a group activity")
:ok
end
@ -214,6 +218,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
do: do_maybe_relay_if_group_activity(object, hd(attributed_to))
defp do_maybe_relay_if_group_activity(object, attributed_to) when is_binary(attributed_to) do
Logger.debug("Let's try to relay group activity")
id = "#{Endpoint.url()}/announces/#{Ecto.UUID.generate()}"
case Actors.get_local_group_by_url(attributed_to) do
@ -223,8 +228,9 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
Logger.info("Forwarded activity to external members of the group")
:ok
{:error, _err} ->
{:error, err} ->
Logger.info("Failed to forward activity to external members of the group")
Logger.debug(inspect(err))
:error
end
@ -233,7 +239,9 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
end
end
defp do_maybe_relay_if_group_activity(_, _), do: :ok
defp do_maybe_relay_if_group_activity(_, attributed_to) do
Logger.debug("Will not relay group activity, attributed to is : #{inspect(attributed_to)}")
end
@spec remote_actors(list(String.t())) :: list(Actor.t())
def remote_actors(recipients) do
@ -439,18 +447,23 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
) do
{to, cc} =
if public do
Logger.debug("Making announce data for a public object")
{[actor.followers_url, object_actor_url],
["https://www.w3.org/ns/activitystreams#Public"]}
else
Logger.debug("Making announce data for a private object")
if actor_type == :Group do
Logger.debug("Making announce data for a group private object")
to =
(object["to"] || [])
|> MapSet.new()
|> MapSet.intersection(MapSet.new([actor.followers_url, actor.members_url]))
|> MapSet.to_list()
Map.get(object, "to", []) ++
Map.get(object, "cc", []) ++ [actor.followers_url, actor.members_url]
{to, []}
else
Logger.debug("Making announce data for a private object")
{[actor.followers_url], []}
end
end
@ -463,6 +476,11 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
"cc" => cc
}
data =
if object["attributedTo"],
do: Map.put(data, "attributedTo", object["attributedTo"]),
else: data
if activity_id, do: Map.put(data, "id", activity_id), else: data
end

View file

@ -196,6 +196,10 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
|> Map.put(:origin_comment_id, origin_comment_id)
|> Map.put(:discussion_id, discussion_id)
# Reply to a deleted entity
{:ok, %Mobilizon.Tombstone{}} ->
data
# Anything else is kind of a MP
{:error, parent} ->
Logger.warn("Parent object is something we don't handle")

View file

@ -107,7 +107,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
def model_to_as(%EventModel{} = event) do
{to, cc} =
if event.visibility == :public,
do: {[@ap_public], []},
do: {[@ap_public], [event.organizer_actor.followers_url]},
else: {[attributed_to_or_default(event).followers_url], [@ap_public]}
%{

View file

@ -10,7 +10,6 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
@behaviour HTTPSignatures.Adapter
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
@ -53,16 +52,8 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
{:ok, String.t()}
| {:error, :actor_not_found | :pem_decode_error}
defp get_public_key_for_url(url) do
case Actors.get_actor_by_url(url) do
{:ok, %Actor{} = actor} ->
get_actor_public_key(actor)
{:error, :actor_not_found} ->
Logger.info(
"Unable to get actor with URL #{url} from local database, returning empty keys to trigger refreshment"
)
{:ok, ""}
with {:ok, %Actor{} = actor} <- ActivityPubActor.get_or_fetch_actor_by_url(url) do
get_actor_public_key(actor)
end
end

View file

@ -10,8 +10,8 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentity do
import Plug.Conn
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Utils
alias Mobilizon.Federation.HTTPSignatures.Signature
@ -41,7 +41,7 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentity do
# We don't need to call refreshment here since
# the Mobilizon.Federation.HTTPSignatures.Signature plug
# should already have refreshed the actor if needed
ActivityPubActor.make_actor_from_url(key_actor_id, ignore_sign_object_fetches: true)
Actors.get_actor_by_url(key_actor_id)
nil ->
{:error, :no_key_in_conn}

View file

@ -119,7 +119,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert object["cc"] == []
# assert object["cc"] == []
assert object["actor"] == "https://demo.gancio.org/federation/u/gancio"
assert object["location"]["name"] == "Colosseo"
@ -146,11 +146,14 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
preferred_username: "member"
)
relay = Relay.get_actor()
with_mock ActivityPubActor, [:passthrough],
get_or_fetch_actor_by_url: fn url ->
case url do
^group_url -> {:ok, group}
^actor_url -> {:ok, actor}
"https://www.w3.org/ns/activitystreams#Public" -> {:ok, relay}
end
end do
data = File.read!("test/fixtures/mobilizon-post-activity-group.json") |> Jason.decode!()

View file

@ -37,7 +37,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.EventsTest do
"attachment" => [],
"attributedTo" => ^actor_url,
"category" => nil,
"cc" => [],
"cc" => [^followers_url],
"commentsEnabled" => false,
"content" => nil,
"draft" => false,
@ -108,7 +108,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.EventsTest do
end
test "from a group member" do
%Actor{id: organizer_actor_id, url: actor_url} = actor = insert(:actor)
%Actor{id: organizer_actor_id, url: actor_url, followers_url: actor_followers_url} =
actor = insert(:actor)
%Actor{
id: attributed_to_id,
@ -139,7 +140,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.EventsTest do
"attachment" => [],
"attributedTo" => ^group_url,
"category" => nil,
"cc" => [],
"cc" => [^actor_followers_url],
"commentsEnabled" => false,
"content" => nil,
"draft" => false,
@ -189,7 +190,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.EventsTest do
"actor" => ^actor_url,
"anonymousParticipationEnabled" => false,
"attributedTo" => ^actor_url,
"cc" => [],
"cc" => [^followers_url],
"commentsEnabled" => false,
"draft" => false,
"ical:status" => "CONFIRMED",
@ -212,7 +213,9 @@ defmodule Mobilizon.Federation.ActivityPub.Types.EventsTest do
test "from a group member" do
%Actor{} = actor_1 = insert(:actor)
%Actor{id: organizer_actor_2_id, url: actor_2_url} = actor_2 = insert(:actor)
%Actor{id: organizer_actor_2_id, url: actor_2_url, followers_url: actor_followers_url} =
actor_2 = insert(:actor)
%Actor{
url: group_url,
@ -247,7 +250,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.EventsTest do
"actor" => ^actor_2_url,
"anonymousParticipationEnabled" => false,
"attributedTo" => ^group_url,
"cc" => [],
"cc" => [^actor_followers_url],
"commentsEnabled" => false,
"draft" => false,
"ical:status" => "CONFIRMED",
@ -269,7 +272,9 @@ defmodule Mobilizon.Federation.ActivityPub.Types.EventsTest do
end
test "from a remote group member" do
%Actor{id: organizer_actor_1_id, url: actor_1_url} = actor_1 = insert(:actor)
%Actor{id: organizer_actor_1_id, url: actor_1_url, followers_url: actor_followers_url} =
actor_1 = insert(:actor)
%Actor{} = actor_2 = insert(:actor)
%Actor{
@ -305,7 +310,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.EventsTest do
"actor" => ^actor_1_url,
"anonymousParticipationEnabled" => false,
"attributedTo" => ^group_url,
"cc" => [],
"cc" => [^actor_followers_url],
"commentsEnabled" => false,
"draft" => false,
"ical:status" => "CONFIRMED",

View file

@ -1,112 +0,0 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
},
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"IdentityProof": "toot:IdentityProof",
"discoverable": "toot:discoverable",
"Device": "toot:Device",
"Ed25519Signature": "toot:Ed25519Signature",
"Ed25519Key": "toot:Ed25519Key",
"Curve25519Key": "toot:Curve25519Key",
"EncryptedMessage": "toot:EncryptedMessage",
"publicKeyBase64": "toot:publicKeyBase64",
"deviceId": "toot:deviceId",
"claim": {
"@type": "@id",
"@id": "toot:claim"
},
"fingerprintKey": {
"@type": "@id",
"@id": "toot:fingerprintKey"
},
"identityKey": {
"@type": "@id",
"@id": "toot:identityKey"
},
"devices": {
"@type": "@id",
"@id": "toot:devices"
},
"messageFranking": "toot:messageFranking",
"messageType": "toot:messageType",
"cipherText": "toot:cipherText",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
}
}
],
"id": "https://framapiaf.org/users/admin",
"type": "Service",
"following": "https://framapiaf.org/users/admin/following",
"followers": "https://framapiaf.org/users/admin/followers",
"inbox": "https://framapiaf.org/users/admin/inbox",
"outbox": "https://framapiaf.org/users/admin/outbox",
"featured": "https://framapiaf.org/users/admin/collections/featured",
"preferredUsername": "admin",
"name": "Administrateur",
"summary": "<p>Je ne suis qu&apos;un compte inutile. Merci nous de contacter via <a href=\"https://contact.framasoft.org/\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">contact.framasoft.org/</span><span class=\"invisible\"></span></a></p>",
"url": "https://framapiaf.org/@admin",
"manuallyApprovesFollowers": false,
"discoverable": null,
"devices": "https://framapiaf.org/users/admin/collections/devices",
"publicKey": {
"id": "https://framapiaf.org/users/admin#main-key",
"owner": "https://framapiaf.org/users/admin",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyHaU/AZ5dWtSxZXkPa89\nDUQ4z+JQHGGUG/xkGuq0v8P6qJfQqtHPBO5vH0IQJqluXWQS96gqTwjZnYevcpNA\nveYv0K25DWszx5Ehz6JX2/sSvu2rNUcQ3YZvSjdo/Yy1u5Fuc5lLmvw8uFzXYekD\nWovTMOnp4mIKpVEm/G/v4w8jvFEKw88h743vwaEIim88GEQItMxzGAV6zSqV1DWO\nLxtoRsinslJYfAG46ex4YUATFveWvOUeWk5W1sEa5f3c0moaTmBM/PAAo8vLxhlw\nJhsHihsCH+BcXKVMjW8OCqYYqISMxEifUBX63HcJt78ELHpOuc1c2eG59PomtTjQ\nywIDAQAB\n-----END PUBLIC KEY-----\n"
},
"tag": [],
"attachment": [
{
"type": "PropertyValue",
"name": "News",
"value": "<span class=\"h-card\"><a href=\"https://framapiaf.org/@Framasoft\" class=\"u-url mention\">@<span>Framasoft</span></a></span>"
},
{
"type": "PropertyValue",
"name": "Support",
"value": "<a href=\"https://contact.framasoft.org/\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">contact.framasoft.org/</span><span class=\"invisible\"></span></a>"
},
{
"type": "PropertyValue",
"name": "Soutenir",
"value": "<a href=\"https://soutenir.framasoft.org/\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">soutenir.framasoft.org/</span><span class=\"invisible\"></span></a>"
},
{
"type": "PropertyValue",
"name": "Site",
"value": "<a href=\"https://framasoft.org/\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">framasoft.org/</span><span class=\"invisible\"></span></a>"
}
],
"endpoints": {
"sharedInbox": "https://framapiaf.org/inbox"
},
"icon": {
"type": "Image",
"mediaType": "image/jpeg",
"url": "https://framapiaf.s3.framasoft.org/framapiaf/accounts/avatars/000/000/002/original/85fbb27ad5e3cf71.jpg"
},
"image": {
"type": "Image",
"mediaType": "image/jpeg",
"url": "https://framapiaf.s3.framasoft.org/framapiaf/accounts/headers/000/000/002/original/6aba75f1ab1ab6de.jpg"
}
}

View file

@ -1,55 +0,0 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#",
"featured": { "@id": "toot:featured", "@type": "@id" },
"alsoKnownAs": { "@id": "as:alsoKnownAs", "@type": "@id" },
"movedTo": { "@id": "as:movedTo", "@type": "@id" },
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"IdentityProof": "toot:IdentityProof",
"discoverable": "toot:discoverable",
"Device": "toot:Device",
"Ed25519Signature": "toot:Ed25519Signature",
"Ed25519Key": "toot:Ed25519Key",
"Curve25519Key": "toot:Curve25519Key",
"EncryptedMessage": "toot:EncryptedMessage",
"publicKeyBase64": "toot:publicKeyBase64",
"deviceId": "toot:deviceId",
"claim": { "@type": "@id", "@id": "toot:claim" },
"fingerprintKey": { "@type": "@id", "@id": "toot:fingerprintKey" },
"identityKey": { "@type": "@id", "@id": "toot:identityKey" },
"devices": { "@type": "@id", "@id": "toot:devices" },
"messageFranking": "toot:messageFranking",
"messageType": "toot:messageType",
"cipherText": "toot:cipherText",
"focalPoint": { "@container": "@list", "@id": "toot:focalPoint" }
}
],
"id": "https://niu.moe/users/rye",
"type": "Person",
"following": "https://niu.moe/users/rye/following",
"followers": "https://niu.moe/users/rye/followers",
"inbox": "https://niu.moe/users/rye/inbox",
"outbox": "https://niu.moe/users/rye/outbox",
"featured": "https://niu.moe/users/rye/collections/featured",
"preferredUsername": "rye",
"name": "♡ rye ♡",
"summary": "\\u003cp\\u003ecome back with a warrant\\u003c/p\\u003e",
"url": "https://niu.moe/@rye",
"manuallyApprovesFollowers": false,
"discoverable": false,
"devices": "https://niu.moe/users/rye/collections/devices",
"publicKey": {
"id": "https://niu.moe/users/rye#main-key",
"owner": "https://niu.moe/users/rye",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA83uRWjCFO35FwfA38mzv\\nEL0TUaXB7+2hYvPwNrn1WY6me5DRbqB5zzMrzWMGr0HSooqNqEYBafGsmVTWUqIk\\nKM9ehtIBraJI+mT5X7DPR3LrXOJF4a9EEslg8XvAk8MN9IrAhm6UljnvB67RtDcA\\nTNB01VWy9yWnxFRtz9o/EMoBPyw5giOaXE2ibVNP8lQIqGKuuBKPzPjSJygdvQ5q\\nxfow2z1TpKRqdsNDqn4n6U6zCXYTzkr0J71/tGw7fsgfv78l0Wjrc7EcuBk74OaG\\nC65UDiu3X4Q6kxCfCEhPSfuwLN+UZkzxcn6goWR0iYpWs57+4tFKu9nJYP4QJ0K9\\nTwIDAQAB\\n-----END PUBLIC KEY-----\\n"
},
"tag": [],
"attachment": [],
"endpoints": { "sharedInbox": "https://niu.moe/inbox" }
}

View file

@ -5,9 +5,8 @@
defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentityTest do
use Mobilizon.Web.ConnCase
import Mox
import Mobilizon.Factory
alias Mobilizon.Service.HTTP.ActivityPub.Mock
alias Mobilizon.Web.Plugs.MappedSignatureToIdentity
defp set_signature(conn, key_id) do
@ -16,30 +15,8 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentityTest do
|> assign(:valid_signature, true)
end
defp framapiaf_admin do
"test/fixtures/signature/framapiaf_admin.json"
|> File.read!()
|> Jason.decode!()
end
defp nyu_rye do
"test/fixtures/signature/nyu_rye.json"
|> File.read!()
|> Jason.decode!()
end
test "it successfully maps a valid identity with a valid signature" do
Mock
|> expect(:call, fn
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: framapiaf_admin()}}
end)
Mock
|> expect(:call, fn
%{method: :get, url: "/doesntmattter"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: ""}}
end)
insert(:actor, domain: "framapiaf.org", url: "https://framapiaf.org/users/admin")
conn =
build_conn(:get, "/doesntmattter")
@ -50,17 +27,7 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentityTest do
end
test "it successfully maps a valid identity with a valid signature with payload" do
Mock
|> expect(:call, fn
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: framapiaf_admin()}}
end)
Mock
|> expect(:call, fn
%{method: :post, url: "/doesntmattter"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: ""}}
end)
insert(:actor, domain: "framapiaf.org", url: "https://framapiaf.org/users/admin")
conn =
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
@ -71,17 +38,8 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentityTest do
end
test "it considers a mapped identity to be invalid when it mismatches a payload" do
Mock
|> expect(:call, fn
%{method: :get, url: "https://niu.moe/users/rye"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: nyu_rye()}}
end)
Mock
|> expect(:call, fn
%{method: :post, url: "/doesntmattter"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: ""}}
end)
insert(:actor, domain: "framapiaf.org", url: "https://framapiaf.org/users/admin")
insert(:actor, domain: "niu.moe", url: "https://niu.moe/users/rye")
conn =
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
@ -91,19 +49,9 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentityTest do
assert %{valid_signature: false} == conn.assigns
end
@tag skip: "Available again when lib/web/plugs/mapped_signature_to_identity.ex#62 is fixed"
test "it considers a mapped identity to be invalid when the identity cannot be found" do
Mock
|> expect(:call, fn
%{method: :get, url: "https://mastodon.social/users/gargron"}, _opts ->
{:ok, %Tesla.Env{status: 404, body: ""}}
end)
Mock
|> expect(:call, fn
%{method: :post, url: "/doesntmattter"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: ""}}
end)
insert(:actor, domain: "framapiaf.org", url: "https://framapiaf.org/users/admin")
insert(:actor, domain: "mastodon.social", url: "https://mastodon.social/users/gargron")
conn =
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})