Improve group refreshment and fixed date signature generation

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-11-13 18:45:01 +01:00
parent 6d599441a9
commit 55af776df9
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
27 changed files with 1009 additions and 704 deletions

View File

@ -90,6 +90,8 @@ config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "/var/lib/mobil
config :tz_world, data_dir: "/var/lib/mobilizon/timezones"
config :mobilizon, Timex.Gettext, default_locale: "en"
config :mobilizon, :media_proxy,
enabled: true,
proxy_opts: [

View File

@ -32,14 +32,14 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
case Actors.get_actor_by_url(url, preload) do
{:ok, %Actor{} = cached_actor} ->
if Actors.needs_update?(cached_actor) do
__MODULE__.make_actor_from_url(url, preload)
__MODULE__.make_actor_from_url(url, preload: preload)
else
{:ok, cached_actor}
end
{:error, :actor_not_found} ->
# For tests, see https://github.com/jjh42/mock#not-supported---mocking-internal-function-calls and Mobilizon.Federation.ActivityPubTest
__MODULE__.make_actor_from_url(url, preload)
__MODULE__.make_actor_from_url(url, preload: preload)
end
end
@ -48,15 +48,15 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
@doc """
Create an actor locally by its URL (AP ID)
"""
@spec make_actor_from_url(url :: String.t(), preload :: boolean()) ::
@spec make_actor_from_url(url :: String.t(), options :: Keyword.t()) ::
{:ok, Actor.t()} | {:error, make_actor_errors | Ecto.Changeset.t()}
def make_actor_from_url(url, preload \\ false) do
def make_actor_from_url(url, options \\ []) do
if are_same_origin?(url, Endpoint.url()) do
{:error, :actor_is_local}
else
case Fetcher.fetch_and_prepare_actor_from_url(url) do
case Fetcher.fetch_and_prepare_actor_from_url(url, options) do
{:ok, data} when is_map(data) ->
Actors.upsert_actor(data, preload)
Actors.upsert_actor(data, Keyword.get(options, :preload, false))
# Request returned 410
{:error, :actor_deleted} ->
@ -78,13 +78,13 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
case Actors.get_actor_by_name_with_preload(nickname, type) do
%Actor{url: actor_url} = actor ->
if Actors.needs_update?(actor) do
make_actor_from_url(actor_url, true)
make_actor_from_url(actor_url, preload: true)
else
{:ok, actor}
end
nil ->
make_actor_from_nickname(nickname, true)
make_actor_from_nickname(nickname, preload: true)
end
end
@ -100,7 +100,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
def make_actor_from_nickname(nickname, preload \\ false) do
case WebFinger.finger(nickname) do
{:ok, url} when is_binary(url) ->
make_actor_from_url(url, preload)
make_actor_from_url(url, preload: preload)
{:error, e} ->
{:error, e}

View File

@ -49,8 +49,11 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
{:error, :content_not_json}
{:ok, %Tesla.Env{} = res} ->
Logger.debug("Resource returned bad HTTP code inspect #{res}")
Logger.debug("Resource returned bad HTTP code #{inspect(res)}")
{:error, :http_error}
{:error, err} ->
{:error, err}
end
else
{:error, :invalid_url}
@ -122,39 +125,25 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
"""
@spec fetch_and_prepare_actor_from_url(String.t()) ::
{:ok, map()} | {:error, fetch_actor_errors}
def fetch_and_prepare_actor_from_url(url) do
def fetch_and_prepare_actor_from_url(url, options \\ []) do
Logger.debug("Fetching and preparing actor from url")
Logger.debug(inspect(url))
case Tesla.get(url,
headers: [{"Accept", "application/activity+json"}],
follow_redirect: true
) do
{:ok, %{status: 200, body: body}} ->
Logger.debug("response okay, now decoding json")
case fetch(url, options) do
{:ok, data} ->
case ActorConverter.as_to_model_data(data) do
{:error, :actor_not_allowed_type} ->
{:error, :actor_not_allowed_type}
case Jason.decode(body) do
{:ok, data} when is_map(data) ->
Logger.debug("Got activity+json response at actor's endpoint, now converting data")
case ActorConverter.as_to_model_data(data) do
{:error, :actor_not_allowed_type} ->
{:error, :actor_not_allowed_type}
map when is_map(map) ->
{:ok, map}
end
{:error, %Jason.DecodeError{} = e} ->
Logger.warn("Could not decode actor at fetch #{url}, #{inspect(e)}")
{:error, :json_decode_error}
map when is_map(map) ->
{:ok, map}
end
{:ok, %{status: 410}} ->
{:error, :http_gone} ->
Logger.info("Response HTTP 410")
{:error, :actor_deleted}
{:ok, %Tesla.Env{}} ->
{:error, :http_error} ->
Logger.info("Non 200 HTTP Code")
{:error, :http_error}

View File

@ -52,7 +52,7 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
@spec fetch_group(String.t(), Actor.t()) :: :ok | {:error, fetch_actor_errors}
def fetch_group(group_url, %Actor{} = on_behalf_of) do
case ActivityPubActor.make_actor_from_url(group_url) do
case ActivityPubActor.make_actor_from_url(group_url, on_behalf_of: on_behalf_of) do
{:error, err}
when err in [:actor_deleted, :http_error, :json_decode_error, :actor_is_local] ->
Logger.debug("Error while making actor")

View File

@ -114,8 +114,12 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
Logger.debug("headers")
Logger.debug(inspect(headers))
with {:ok, key} <- prepare_public_key(keys) do
HTTPSignatures.sign(key, actor.url <> "#main-key", headers)
case prepare_public_key(keys) do
{:ok, key} ->
HTTPSignatures.sign(key, actor.url <> "#main-key", headers)
{:error, :pem_decode_error} ->
raise ArgumentError, message: "Failed to prepare public keys for #{actor.url}"
end
end
@ -129,7 +133,7 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
@spec generate_date_header(NaiveDateTime.t()) :: String.t()
def generate_date_header(%NaiveDateTime{} = date) do
Timex.format!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
Timex.lformat!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT", "en")
end
@spec generate_request_target(String.t(), String.t()) :: String.t()

View File

@ -48,6 +48,7 @@ defmodule Mobilizon.Web.Plugs.HTTPSignatures do
signature_valid = HTTPSignatures.validate_conn(conn)
Logger.debug("Is signature valid ? #{inspect(signature_valid)}")
date_valid = date_valid?(conn)
Logger.debug("Is date valid ? #{inspect(date_valid)}")
assign(conn, :valid_signature, signature_valid && date_valid)
end
end

View File

@ -48,9 +48,10 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentity do
# if this has payload make sure it is signed by the same actor that made it
def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do
with actor_id <- Utils.get_url(actor),
with actor_id when actor_id != nil <- Utils.get_url(actor),
{:actor, %Actor{} = actor} <- {:actor, actor_from_key_id(conn)},
{:actor_match, true} <- {:actor_match, actor.url == actor_id} do
Logger.debug("Mapped identity to #{actor.url} from actor param")
assign(conn, :actor, actor)
else
{:actor_match, false} ->
@ -58,7 +59,7 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentity do
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
assign(conn, :valid_signature, false)
# remove me once testsuite uses mapped capabilities instead of what we do now
# TODO: remove me once testsuite uses mapped capabilities instead of what we do now
{:actor, nil} ->
Logger.debug("Failed to map identity from signature (lookup failure)")
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
@ -70,6 +71,7 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentity do
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
case actor_from_key_id(conn) do
%Actor{} = actor ->
Logger.debug("Mapped identity to #{actor.url} from signed fetch")
assign(conn, :actor, actor)
_ ->

View File

@ -12,6 +12,7 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
alias Mobilizon.Resources.Resource
alias Mobilizon.Storage.Page
alias Mobilizon.Todos.TodoList
require Logger
@private_visibility_empty_collection %{elements: [], total: 0}
@json_ld_header Utils.make_json_ld_header()
@ -39,9 +40,16 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
page = Map.get(args, :page, 1)
collection_name = String.trim_trailing(view_name, ".json")
collection_name = String.to_existing_atom(collection_name)
actor_applicant = Map.get(args, :actor_applicant)
Logger.debug("Rendering actor collection #{inspect(collection_name)}")
Logger.debug(
"Using authenticated fetch with actor #{if actor_applicant, do: actor_applicant.url, else: nil}"
)
%{total: total, elements: elements} =
if can_get_collection?(collection_name, actor, Map.get(args, :actor_applicant)),
if can_get_collection?(collection_name, actor, actor_applicant),
do: fetch_collection(collection_name, actor, page),
else: default_collection(collection_name, actor, page)
@ -127,8 +135,13 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
when visibility in [:public, :unlisted] and collection in [:outbox, :followers, :following],
do: true
defp can_get_collection?(_collection_name, %Actor{} = actor, %Actor{} = actor_applicant),
do: actor_applicant_group_member?(actor, actor_applicant)
defp can_get_collection?(_collection_name, %Actor{} = actor, %Actor{} = actor_applicant) do
Logger.debug(
"Testing if #{actor_applicant.url} can be allowed access to #{actor.url} private collections"
)
actor_applicant_group_member?(actor, actor_applicant)
end
defp can_get_collection?(_, _, _), do: false

View File

@ -47,10 +47,17 @@ defmodule Mobilizon.Federation.ActivityPubTest do
File.read!("test/fixtures/mastodon-status-2.json")
|> Jason.decode!()
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
Mock
|> expect(:call, fn
|> expect(:call, 2, fn
%{method: :get, url: ^url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}}
%{method: :get, url: "https://framapiaf.org/users/Framasoft"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
{:ok, object} = ActivityPub.fetch_object_from_url(url)
@ -72,13 +79,20 @@ defmodule Mobilizon.Federation.ActivityPubTest do
File.read!("test/fixtures/mastodon-status-4.json")
|> Jason.decode!()
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
Mock
|> expect(:call, 2, fn
|> expect(:call, 3, fn
%{method: :get, url: ^url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}}
%{method: :get, url: ^reply_to_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: reply_to_data}}
%{method: :get, url: "https://pirateradio.social/users/captain"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
{:ok, object} = ActivityPub.fetch_object_from_url(url)
@ -98,13 +112,26 @@ defmodule Mobilizon.Federation.ActivityPubTest do
File.read!("test/fixtures/peertube-video.json")
|> Jason.decode!()
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
Mock
|> expect(:call, 2, fn
|> expect(:call, 5, fn
%{method: :get, url: ^url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}}
%{method: :get, url: ^origin_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: origin_data}}
%{method: :get, url: "https://diaspodon.fr/users/dada"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
%{method: :get, url: "https://framatube.org/accounts/framasoft"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
%{method: :get, url: "https://framapiaf.org/users/Pouhiou"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
{:ok, object} = ActivityPub.fetch_object_from_url(url)

View File

@ -1,39 +1,72 @@
defmodule Mobilizon.Federation.ActivityPub.ActorTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
use Mobilizon.DataCase
import Mox
import Mock
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.{Fetcher, Relay}
alias Mobilizon.Federation.ActivityPub.Relay
alias Mobilizon.Service.HTTP.ActivityPub.Mock
describe "fetching actor from its url" do
@actor_url "https://framapiaf.org/users/tcit"
test "returns an actor from nickname" do
use_cassette "activity_pub/fetch_tcit@framapiaf.org" do
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :public} =
_actor} = ActivityPubActor.make_actor_from_nickname("tcit@framapiaf.org")
end
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
|> Map.put("id", @actor_url)
|> Map.put("preferredUsername", "tcit")
|> Map.put("discoverable", true)
use_cassette "activity_pub/fetch_tcit@framapiaf.org_not_discoverable" do
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted} =
_actor} = ActivityPubActor.make_actor_from_nickname("tcit@framapiaf.org")
end
Mock
|> expect(:call, fn
%{method: :get, url: @actor_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :public} =
_actor} = ActivityPubActor.make_actor_from_nickname("tcit@framapiaf.org")
end
test "returns an actor from nickname when not discoverable" do
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
|> Map.put("id", @actor_url)
|> Map.put("preferredUsername", "tcit")
Mock
|> expect(:call, fn
%{method: :get, url: @actor_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted} =
_actor} = ActivityPubActor.make_actor_from_nickname("tcit@framapiaf.org")
end
@actor_url "https://framapiaf.org/users/tcit"
test "returns an actor from url" do
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
|> Map.put("id", @actor_url)
|> Map.put("preferredUsername", "tcit")
Mock
|> expect(:call, fn
%{method: :get, url: @actor_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
# Initial fetch
use_cassette "activity_pub/fetch_framapiaf.org_users_tcit" do
# Unlisted because discoverable is not present in the JSON payload
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted}} =
ActivityPubActor.get_or_fetch_actor_by_url(@actor_url)
end
# Unlisted because discoverable is not present in the JSON payload
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted}} =
ActivityPubActor.get_or_fetch_actor_by_url(@actor_url)
# Fetch uses cache if Actors.needs_update? returns false
with_mocks([
@ -49,7 +82,7 @@ defmodule Mobilizon.Federation.ActivityPub.ActorTest do
needs_update?: fn _ -> false end
]},
{ActivityPubActor, [:passthrough],
make_actor_from_url: fn @actor_url, false ->
make_actor_from_url: fn @actor_url, preload: false ->
{:ok,
%Actor{
preferred_username: "tcit",
@ -61,7 +94,7 @@ defmodule Mobilizon.Federation.ActivityPub.ActorTest do
ActivityPubActor.get_or_fetch_actor_by_url(@actor_url)
assert_called(Actors.needs_update?(:_))
refute called(ActivityPubActor.make_actor_from_url(@actor_url, false))
refute called(ActivityPubActor.make_actor_from_url(@actor_url, preload: false))
end
# Fetch doesn't use cache if Actors.needs_update? returns true
@ -78,7 +111,7 @@ defmodule Mobilizon.Federation.ActivityPub.ActorTest do
needs_update?: fn _ -> true end
]},
{ActivityPubActor, [:passthrough],
make_actor_from_url: fn @actor_url, false ->
make_actor_from_url: fn @actor_url, preload: false ->
{:ok,
%Actor{
preferred_username: "tcit",
@ -92,24 +125,21 @@ defmodule Mobilizon.Federation.ActivityPub.ActorTest do
assert_called(ActivityPubActor.get_or_fetch_actor_by_url(@actor_url))
assert_called(Actors.get_actor_by_url(@actor_url, false))
assert_called(Actors.needs_update?(:_))
assert_called(ActivityPubActor.make_actor_from_url(@actor_url, false))
assert_called(ActivityPubActor.make_actor_from_url(@actor_url, preload: false))
end
end
test "handles remote actor being deleted" do
with_mocks([
{Fetcher, [:passthrough],
fetch_and_prepare_actor_from_url: fn @actor_url ->
{:error, :actor_deleted}
end}
]) do
assert match?(
{:error, :actor_deleted},
ActivityPubActor.make_actor_from_url(@actor_url, false)
)
Mock
|> expect(:call, fn
%{method: :get, url: @actor_url}, _opts ->
{:ok, %Tesla.Env{status: 410, body: ""}}
end)
assert_called(Fetcher.fetch_and_prepare_actor_from_url(@actor_url))
end
assert match?(
{:error, :actor_deleted},
ActivityPubActor.make_actor_from_url(@actor_url, preload: false)
)
end
@public_url "https://www.w3.org/ns/activitystreams#Public"

View File

@ -36,12 +36,79 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.CommentsTest do
end
test "it fetches replied-to activities if we don't have them" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
actor_data = File.read!("test/fixtures/mastodon-actor.json") |> Jason.decode!()
status_data = File.read!("test/fixtures/mastodon-status-2.json") |> Jason.decode!()
reply_to_data =
File.read!("test/fixtures/pleroma-comment-object.json")
|> Jason.decode!()
reply_to_url = "https://fedi.absturztau.be/objects/1726cdc7-4f2a-4ddb-9c68-03d27c98c3d9"
Mock
|> expect(:call, 5, fn
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body:
actor_data
|> Map.put("id", "https://framapiaf.org/users/admin")
|> Map.put("preferredUsername", "admin")
}}
%{method: :get, url: "https://framapiaf.org/users/tcit"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body:
actor_data
|> Map.put("id", "https://framapiaf.org/users/tcit")
|> Map.put("preferredUsername", "tcit")
}}
%{method: :get, url: "https://framapiaf.org/users/Framasoft"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body:
actor_data
|> Map.put("id", "https://framapiaf.org/users/Framasoft")
|> Map.put("preferredUsername", "Framasoft")
}}
%{
method: :get,
url: "https://fedi.absturztau.be/objects/1726cdc7-4f2a-4ddb-9c68-03d27c98c3d9"
},
_opts ->
{:ok,
%Tesla.Env{
status: 200,
body:
status_data
|> Map.put(
"id",
"https://fedi.absturztau.be/objects/1726cdc7-4f2a-4ddb-9c68-03d27c98c3d9"
)
|> Map.put("actor", "https://fedi.absturztau.be/users/dqn")
}}
%{method: :get, url: "https://fedi.absturztau.be/users/dqn"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body: Map.put(actor_data, "id", "https://fedi.absturztau.be/users/dqn")
}}
%{method: :get, url: ^reply_to_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: reply_to_data}}
end)
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Jason.decode!()
object =
data["object"]
|> Map.put("inReplyTo", reply_to_url)
@ -50,16 +117,6 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.CommentsTest do
data
|> Map.put("object", object)
reply_to_data =
File.read!("test/fixtures/pleroma-comment-object.json")
|> Jason.decode!()
Mock
|> expect(:call, fn
%{method: :get, url: ^reply_to_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: reply_to_data}}
end)
{:ok, returned_activity, _} = Transmogrifier.handle_incoming(data)
%Comment{} =
@ -75,6 +132,25 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.CommentsTest do
end
test "it doesn't saves replies to an event if the event doesn't accept comments" do
actor_data = File.read!("test/fixtures/mastodon-actor.json") |> Jason.decode!()
Mock
|> expect(:call, 2, fn
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body: Map.put(actor_data, "id", "https://framapiaf.org/users/admin")
}}
%{method: :get, url: "https://framapiaf.org/users/tcit"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body: Map.put(actor_data, "id", "https://framapiaf.org/users/tcit")
}}
end)
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Jason.decode!()
@ -94,6 +170,31 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.CommentsTest do
@url_404 "https://404.site/whatever"
test "it does not crash if the object in inReplyTo can't be fetched" do
actor_data = File.read!("test/fixtures/mastodon-actor.json") |> Jason.decode!()
Mock
|> expect(:call, 2, fn
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body:
actor_data
|> Map.put("id", "https://framapiaf.org/users/admin")
|> Map.put("preferredUsername", "admin")
}}
%{method: :get, url: "https://framapiaf.org/users/tcit"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body:
actor_data
|> Map.put("id", "https://framapiaf.org/users/tcit")
|> Map.put("preferredUsername", "tcit")
}}
end)
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Jason.decode!()
@ -118,6 +219,31 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.CommentsTest do
end
test "it ignores incoming private notes" do
actor_data = File.read!("test/fixtures/mastodon-actor.json") |> Jason.decode!()
Mock
|> expect(:call, 2, fn
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body:
actor_data
|> Map.put("id", "https://framapiaf.org/users/admin")
|> Map.put("preferredUsername", "admin")
}}
%{method: :get, url: "https://framapiaf.org/users/tcit"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body:
actor_data
|> Map.put("id", "https://framapiaf.org/users/tcit")
|> Map.put("preferredUsername", "tcit")
}}
end)
data = File.read!("test/fixtures/mastodon-post-activity-private.json") |> Jason.decode!()
event = insert(:event)
object = data["object"]
@ -128,16 +254,33 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.CommentsTest do
end
test "it works for incoming notices" do
actor_data = File.read!("test/fixtures/mastodon-actor.json") |> Jason.decode!()
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
Mock
|> expect(:call, 2, fn
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body: Map.put(actor_data, "id", "https://framapiaf.org/users/admin")
}}
%{method: :get, url: "https://framapiaf.org/users/tcit"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body: Map.put(actor_data, "id", "https://framapiaf.org/users/tcit")
}}
end)
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
assert data["id"] ==
"https://framapiaf.org/users/admin/statuses/99512778738411822/activity"
assert data["to"] == [
"https://www.w3.org/ns/activitystreams#Public",
"https://framapiaf.org/users/tcit"
"https://www.w3.org/ns/activitystreams#Public"
]
# assert data["cc"] == [
@ -164,6 +307,31 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.CommentsTest do
end
test "it works for incoming notices with hashtags" do
actor_data = File.read!("test/fixtures/mastodon-actor.json") |> Jason.decode!()
Mock
|> expect(:call, 2, fn
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body:
actor_data
|> Map.put("id", "https://framapiaf.org/users/admin")
|> Map.put("preferredUsername", "admin")
}}
%{method: :get, url: "https://framapiaf.org/users/tcit"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body:
actor_data
|> Map.put("id", "https://framapiaf.org/users/tcit")
|> Map.put("preferredUsername", "tcit")
}}
end)
data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)

View File

@ -56,6 +56,19 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
end
test "it fails for incoming deletes with spoofed origin" do
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
Mock
|> expect(:call, 2, fn
%{method: :get, url: "https://framapiaf.org/users/peertube"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
%{method: :get, url: "http://mastodon.example.org/users/gargron"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
comment = insert(:comment)
announce_data =
@ -77,7 +90,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
data
|> Map.put("object", object)
{:error, :unknown_actor} = Transmogrifier.handle_incoming(data)
:error = Transmogrifier.handle_incoming(data)
assert Discussions.get_comment_from_url(comment.url)
end
@ -132,12 +145,12 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
|> Map.put("id", deleted_actor_url)
Mock
|> expect(:call, fn
|> expect(:call, 2, fn
%{url: ^deleted_actor_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: deleted_actor_data}}
end)
assert :error == Transmogrifier.handle_incoming(data)
assert {:error, "Group object URL remote"} == Transmogrifier.handle_incoming(data)
assert Actors.get_actor_by_url(url)
end

View File

@ -1,16 +1,31 @@
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.FollowTest do
use Mobilizon.DataCase
import Mox
import ExUnit.CaptureLog
import Mobilizon.Factory
alias Mobilizon.Actors
alias Mobilizon.Actors.Follower
alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Transmogrifier}
alias Mobilizon.Service.HTTP.ActivityPub.Mock
describe "handle incoming follow requests" do
test "it works only for groups" do
actor = insert(:actor)
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
Mock
|> expect(:call, fn
%{method: :get, url: "https://social.tcit.fr/users/tcit"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body: Map.put(actor_data, "id", "https://social.tcit.fr/users/tcit")
}}
end)
data =
File.read!("test/fixtures/mastodon-follow-activity.json")
|> Jason.decode!()
@ -27,6 +42,20 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.FollowTest do
test "it works for incoming follow requests" do
actor = insert(:group)
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
Mock
|> expect(:call, fn
%{method: :get, url: "https://social.tcit.fr/users/tcit"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body: Map.put(actor_data, "id", "https://social.tcit.fr/users/tcit")
}}
end)
data =
File.read!("test/fixtures/mastodon-follow-activity.json")
|> Jason.decode!()

View File

@ -22,9 +22,12 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UndoTest do
|> Jason.decode!()
Mock
|> expect(:call, fn
|> expect(:call, 2, fn
%{method: :get, url: "https://framapiaf.org/users/Framasoft"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
%{method: :get, url: "https://framapiaf.org/users/peertube"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
{:ok, _, %Comment{}} = Transmogrifier.handle_incoming(announce_data)

View File

@ -1,8 +1,8 @@
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UpdateTest do
use Mobilizon.DataCase
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
use Oban.Testing, repo: Mobilizon.Storage.Repo
import Mobilizon.Factory
import Mox
alias Mobilizon.{Actors, Events, Posts}
alias Mobilizon.Actors.{Actor, Member}
@ -10,79 +10,100 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UpdateTest do
alias Mobilizon.Posts.Post
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier}
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Service.HTTP.ActivityPub.Mock
describe "handle incoming update activities" do
test "it works for incoming update activities on actors" do
use_cassette "activity_pub/update_actor_activity" do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
object =
update_data["object"]
|> Map.put("actor", data["actor"])
|> Map.put("id", data["actor"])
Mock
|> expect(:call, 2, fn
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
update_data =
update_data
|> Map.put("actor", data["actor"])
|> Map.put("object", object)
%{method: :get, url: "https://framapiaf.org/users/tcit"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
{:ok, %Activity{data: _data, local: false}, _} =
Transmogrifier.handle_incoming(update_data)
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
{:ok, %Actor{} = actor} = Actors.get_actor_by_url(update_data["actor"])
assert actor.name == "nextsoft"
object =
update_data["object"]
|> Map.put("actor", data["actor"])
|> Map.put("id", data["actor"])
assert actor.summary == "<p>Some bio</p>"
end
update_data =
update_data
|> Map.put("actor", data["actor"])
|> Map.put("object", object)
{:ok, %Activity{data: _data, local: false}, _} = Transmogrifier.handle_incoming(update_data)
{:ok, %Actor{} = actor} = Actors.get_actor_by_url(update_data["actor"])
assert actor.name == "nextsoft"
assert actor.summary == "<p>Some bio</p>"
end
test "it works for incoming update activities on events" do
use_cassette "activity_pub/event_update_activities" do
data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!()
data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, %Event{id: event_id}} =
Transmogrifier.handle_incoming(data)
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
assert_enqueued(
worker: Mobilizon.Service.Workers.BuildSearch,
args: %{event_id: event_id, op: :insert_search_event}
)
Mock
|> expect(:call, 2, fn
%{method: :get, url: "https://mobilizon.fr/@metacartes"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
assert %{success: 1, snoozed: 0, failure: 0} == Oban.drain_queue(queue: :search)
%{method: :get, url: "https://framapiaf.org/users/tcit"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, %Event{id: event_id}} =
Transmogrifier.handle_incoming(data)
object =
data["object"]
|> Map.put("actor", data["actor"])
|> Map.put("name", "My updated event")
|> Map.put("id", data["object"]["id"])
|> Map.put("type", "Event")
assert_enqueued(
worker: Mobilizon.Service.Workers.BuildSearch,
args: %{event_id: event_id, op: :insert_search_event}
)
update_data =
update_data
|> Map.put("actor", data["actor"])
|> Map.put("object", object)
assert %{success: 1, snoozed: 0, failure: 0} == Oban.drain_queue(queue: :search)
{:ok, %Activity{data: data, local: false}, _} =
Transmogrifier.handle_incoming(update_data)
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
%Event{} = event = Events.get_event_by_url(data["object"]["id"])
object =
data["object"]
|> Map.put("actor", data["actor"])
|> Map.put("name", "My updated event")
|> Map.put("id", data["object"]["id"])
|> Map.put("type", "Event")
assert_enqueued(
worker: Mobilizon.Service.Workers.BuildSearch,
args: %{event_id: event_id, op: :update_search_event}
)
update_data =
update_data
|> Map.put("actor", data["actor"])
|> Map.put("object", object)
assert %{success: 1, snoozed: 0, failure: 0} == Oban.drain_queue(queue: :search)
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(update_data)
assert event.title == "My updated event"
%Event{} = event = Events.get_event_by_url(data["object"]["id"])
assert event.description == data["object"]["content"]
end
assert_enqueued(
worker: Mobilizon.Service.Workers.BuildSearch,
args: %{event_id: event_id, op: :update_search_event}
)
assert %{success: 1, snoozed: 0, failure: 0} == Oban.drain_queue(queue: :search)
assert event.title == "My updated event"
assert event.description == data["object"]["content"]
end
# test "it works for incoming update activities which lock the account" do

View File

@ -4,8 +4,6 @@
# Upstream: https://git.pleroma.social/pleroma/pleroma/blob/develop/test/web/activity_pub/transmogrifier_test.exs
defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
use Mobilizon.DataCase
use Oban.Testing, repo: Mobilizon.Storage.Repo
@ -33,54 +31,75 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
describe "handle incoming events" do
test "it works for incoming events" do
use_cassette "activity_pub/fetch_mobilizon_post_activity" do
data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!()
actor_data = File.read!("test/fixtures/mastodon-actor.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, %Event{} = event} =
Transmogrifier.handle_incoming(data)
Mock
|> expect(:call, 2, fn
%{method: :get, url: url}, _opts ->
case url do
"https://mobilizon.fr/@metacartes" ->
actor_data = Map.put(actor_data, "id", "https://mobilizon.fr/@metacartes")
{:ok, %Tesla.Env{status: 200, body: actor_data}}
assert data["id"] ==
"https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93/activity"
"https://framapiaf.org/users/tcit" ->
actor_data = Map.put(actor_data, "id", "https://framapiaf.org/users/tcit")
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end
end)
assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!()
#
# assert data["cc"] == [
# "https://framapiaf.org/users/admin/followers",
# "http://localtesting.pleroma.lol/users/lain"
# ]
{:ok, %Activity{data: data, local: false}, %Event{} = event} =
Transmogrifier.handle_incoming(data)
assert data["actor"] == "https://mobilizon.fr/@metacartes"
assert data["id"] ==
"https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93/activity"
object = data["object"]
assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert object["id"] ==
"https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93"
#
# assert data["cc"] == [
# "https://framapiaf.org/users/admin/followers",
# "http://localtesting.pleroma.lol/users/lain"
# ]
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert data["actor"] == "https://mobilizon.fr/@metacartes"
# assert object["cc"] == [
# "https://framapiaf.org/users/admin/followers",
# "http://localtesting.pleroma.lol/users/lain"
# ]
object = data["object"]
assert object["actor"] == "https://mobilizon.fr/@metacartes"
assert object["location"]["name"] == "Locaux de Framasoft"
# assert object["attributedTo"] == "https://mobilizon.fr/@metacartes"
assert object["id"] ==
"https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93"
assert event.physical_address.street == "10 Rue Jangot"
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert event.physical_address.url ==
"https://event1.tcit.fr/address/eeecc11d-0030-43e8-a897-6422876372jd"
# assert object["cc"] == [
# "https://framapiaf.org/users/admin/followers",
# "http://localtesting.pleroma.lol/users/lain"
# ]
assert event.online_address == "https://google.com"
assert object["actor"] == "https://mobilizon.fr/@metacartes"
assert object["location"]["name"] == "Locaux de Framasoft"
# assert object["attributedTo"] == "https://mobilizon.fr/@metacartes"
{:ok, %Actor{}} = Actors.get_actor_by_url(object["actor"])
end
assert event.physical_address.street == "10 Rue Jangot"
assert event.physical_address.url ==
"https://event1.tcit.fr/address/eeecc11d-0030-43e8-a897-6422876372jd"
assert event.online_address == "https://google.com"
{:ok, %Actor{}} = Actors.get_actor_by_url(object["actor"])
end
test "it works for incoming events from Gancio" do
data = File.read!("test/fixtures/gancio-event-activity.json") |> Jason.decode!()
actor_data = File.read!("test/fixtures/gancio-actor.json") |> Jason.decode!()
Mock
|> expect(:call, fn
%{method: :get, url: "https://demo.gancio.org/federation/u/gancio"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
{:ok, %Activity{data: data, local: false}, %Event{} = event} =
Transmogrifier.handle_incoming(data)
@ -524,6 +543,12 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
end
test "it accepts incoming resources and handles group being not found" do
Mock
|> expect(:call, fn
%{method: :get, url: "https://someurl.com/notfound"}, _opts ->
{:ok, %Tesla.Env{status: 404, body: ""}}
end)
creator =
insert(:actor,
domain: "mobilizon.app",
@ -649,12 +674,18 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
test "it works for incoming announces" do
data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
status_data = File.read!("test/fixtures/mastodon-status.json") |> Jason.decode!()
actor_data = File.read!("test/fixtures/mastodon-actor.json") |> Jason.decode!()
Mock
|> expect(:call, fn
%{method: :get, url: "https://framapiaf.org/users/peertube/statuses/104584600044284729"},
_opts ->
{:ok, %Tesla.Env{status: 200, body: status_data}}
|> expect(:call, 2, fn
%{method: :get, url: url}, _opts ->
case url do
"https://framapiaf.org/users/peertube/statuses/104584600044284729" ->
{:ok, %Tesla.Env{status: 200, body: status_data}}
"https://framapiaf.org/users/peertube" ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end
end)
{:ok, _, %Comment{actor: %Actor{url: actor_url}, url: comment_url}} =
@ -677,10 +708,12 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
File.read!("test/fixtures/mastodon-announce.json")
|> Jason.decode!()
|> Map.put("object", comment_url)
|> Map.put("actor", actor_url)
Mock
|> expect(:call, fn
%{method: :get, url: ^actor_url}, _opts ->
actor_data = Map.put(actor_data, "id", actor_url)
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end)
@ -926,10 +959,21 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
File.read!("test/fixtures/https__info.pleroma.site_activity.json")
|> Jason.decode!()
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
|> Map.put("id", "https://framapiaf.org/users/admin")
Mock
|> expect(:call, fn
%{method: :get, url: "https://info.pleroma.site/activity.json"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}}
|> expect(:call, 2, fn
%{method: :get, url: url}, _opts ->
case url do
"https://info.pleroma.site/activity.json" ->
{:ok, %Tesla.Env{status: 200, body: data}}
"https://framapiaf.org/users/admin" ->
{:ok, %Tesla.Env{status: 200, body: actor_data}}
end
end)
data = %{

36
test/fixtures/gancio-actor.json vendored Normal file
View File

@ -0,0 +1,36 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"toot": "http://joinmastodon.org/ns#",
"schema": "http://schema.org#",
"ProperyValue": "schema:PropertyValue",
"value": "schema:value"
}
],
"id": "https://demo.gancio.org/federation/u/gancio",
"type": "Application",
"name": "gancio",
"preferredUsername": "gancio",
"inbox": "https://demo.gancio.org/federation/u/gancio/inbox",
"outbox": "https://demo.gancio.org/federation/u/gancio/outbox",
"discoverable": true,
"attachment": [
{
"type": "PropertyValue",
"name": "Website",
"value": "<a href='https://demo.gancio.org'>https://demo.gancio.org</a>"
}
],
"icon": {
"type": "Image",
"mediaType": "image/png",
"url": "https://demo.gancio.org/logo.png"
},
"publicKey": {
"id": "https://demo.gancio.org/federation/u/gancio#main-key",
"owner": "https://demo.gancio.org/federation/u/gancio",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxp9BQ8TvVqu+0xXk7VuZ\nnuO42cHxVI+z/3TQ80AfX5aoUnK/uP7lIPy+NiIgRRu0L4hsjEs+HP6Ny9NAKFtC\nddS3pUrgIDz/AUyKeYRsCycw4XyeX7gaqIan4vCx+ANPDVTc3twDenynHhaXbPsP\nzGeKiAsGIFKRUxc5I5xnQBk6Fy6LZvGwfif07AcECER+nzffSOMPYFVbhlRuBwOg\n/tJcut77KOEpJIQSwqzT0FOw4oFtkvJt/nhpQMkXwOjEuiMOVpPoXUIpWjnbvNmy\nIPXdnKN4QqHi0fAE+FvKGbNmr18vqApT/D4Yen6W1ZWCRdUR1jjl8LNFBkPH/Tad\nkOj+UyRRJjRRqY5mXCI72Bmhwmi/YdS4gt9K73okOZ3atM+9Kfj3azZm8pP7fRkK\n/lwRP8RZFSSpz4w9JtzYmR7P8qTaxwMuq8VrxtFmf1IBChFpyNHUDtmC9MzLBRE7\n+fnpr1bARR3OwO83/xtT+vKNE+2SBvsf7zeFRXa+p5dGaih90rQOwL8EsUItiG61\nm4y9n3Q7BM7XwrZ7sGe3Hey5SWveOEgemfP4ANJBiMQpU69LKM9dGW1FcEX4FlwW\nZx/135nzMXE2cF+y+q/yY2FlacXPqJXMY32mIc+rHMzvFY/ZDzjRY/7Gg2ekjXuN\n1o7Ag7a+5k+r+XkWBNKIHp8CAwEAAQ==\n-----END PUBLIC KEY-----\n"
}
}

119
test/fixtures/mastodon-tcit-tcit.json vendored Normal file
View File

@ -0,0 +1,119 @@
{
"@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"
},
"featuredTags": {
"@id": "toot:featuredTags",
"@type": "@id"
},
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
},
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"schema": "http://schema.org#"