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 :tz_world, data_dir: "/var/lib/mobilizon/timezones"
config :mobilizon, Timex.Gettext, default_locale: "en"
config :mobilizon, :media_proxy, config :mobilizon, :media_proxy,
enabled: true, enabled: true,
proxy_opts: [ proxy_opts: [

View file

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

View file

@ -49,8 +49,11 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
{:error, :content_not_json} {:error, :content_not_json}
{:ok, %Tesla.Env{} = res} -> {: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, :http_error}
{:error, err} ->
{:error, err}
end end
else else
{:error, :invalid_url} {:error, :invalid_url}
@ -122,39 +125,25 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
""" """
@spec fetch_and_prepare_actor_from_url(String.t()) :: @spec fetch_and_prepare_actor_from_url(String.t()) ::
{:ok, map()} | {:error, fetch_actor_errors} {: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("Fetching and preparing actor from url")
Logger.debug(inspect(url)) Logger.debug(inspect(url))
case Tesla.get(url, case fetch(url, options) do
headers: [{"Accept", "application/activity+json"}], {:ok, data} ->
follow_redirect: true case ActorConverter.as_to_model_data(data) do
) do {:error, :actor_not_allowed_type} ->
{:ok, %{status: 200, body: body}} -> {:error, :actor_not_allowed_type}
Logger.debug("response okay, now decoding json")
case Jason.decode(body) do map when is_map(map) ->
{:ok, data} when is_map(data) -> {:ok, map}
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}
end end
{:ok, %{status: 410}} -> {:error, :http_gone} ->
Logger.info("Response HTTP 410") Logger.info("Response HTTP 410")
{:error, :actor_deleted} {:error, :actor_deleted}
{:ok, %Tesla.Env{}} -> {:error, :http_error} ->
Logger.info("Non 200 HTTP Code") Logger.info("Non 200 HTTP Code")
{:error, :http_error} {: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} @spec fetch_group(String.t(), Actor.t()) :: :ok | {:error, fetch_actor_errors}
def fetch_group(group_url, %Actor{} = on_behalf_of) do 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} {:error, err}
when err in [:actor_deleted, :http_error, :json_decode_error, :actor_is_local] -> when err in [:actor_deleted, :http_error, :json_decode_error, :actor_is_local] ->
Logger.debug("Error while making actor") Logger.debug("Error while making actor")

View file

@ -114,8 +114,12 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
Logger.debug("headers") Logger.debug("headers")
Logger.debug(inspect(headers)) Logger.debug(inspect(headers))
with {:ok, key} <- prepare_public_key(keys) do case prepare_public_key(keys) do
HTTPSignatures.sign(key, actor.url <> "#main-key", headers) {: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
end end
@ -129,7 +133,7 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
@spec generate_date_header(NaiveDateTime.t()) :: String.t() @spec generate_date_header(NaiveDateTime.t()) :: String.t()
def generate_date_header(%NaiveDateTime{} = date) do 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 end
@spec generate_request_target(String.t(), String.t()) :: String.t() @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) signature_valid = HTTPSignatures.validate_conn(conn)
Logger.debug("Is signature valid ? #{inspect(signature_valid)}") Logger.debug("Is signature valid ? #{inspect(signature_valid)}")
date_valid = date_valid?(conn) date_valid = date_valid?(conn)
Logger.debug("Is date valid ? #{inspect(date_valid)}")
assign(conn, :valid_signature, signature_valid && date_valid) assign(conn, :valid_signature, signature_valid && date_valid)
end end
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 # 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 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, %Actor{} = actor} <- {:actor, actor_from_key_id(conn)},
{:actor_match, true} <- {:actor_match, actor.url == actor_id} do {:actor_match, true} <- {:actor_match, actor.url == actor_id} do
Logger.debug("Mapped identity to #{actor.url} from actor param")
assign(conn, :actor, actor) assign(conn, :actor, actor)
else else
{:actor_match, false} -> {:actor_match, false} ->
@ -58,7 +59,7 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentity do
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}") Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
assign(conn, :valid_signature, false) 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} -> {:actor, nil} ->
Logger.debug("Failed to map identity from signature (lookup failure)") Logger.debug("Failed to map identity from signature (lookup failure)")
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}") 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 def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
case actor_from_key_id(conn) do case actor_from_key_id(conn) do
%Actor{} = actor -> %Actor{} = actor ->
Logger.debug("Mapped identity to #{actor.url} from signed fetch")
assign(conn, :actor, actor) assign(conn, :actor, actor)
_ -> _ ->

View file

@ -12,6 +12,7 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
alias Mobilizon.Resources.Resource alias Mobilizon.Resources.Resource
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Todos.TodoList alias Mobilizon.Todos.TodoList
require Logger
@private_visibility_empty_collection %{elements: [], total: 0} @private_visibility_empty_collection %{elements: [], total: 0}
@json_ld_header Utils.make_json_ld_header() @json_ld_header Utils.make_json_ld_header()
@ -39,9 +40,16 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
page = Map.get(args, :page, 1) page = Map.get(args, :page, 1)
collection_name = String.trim_trailing(view_name, ".json") collection_name = String.trim_trailing(view_name, ".json")
collection_name = String.to_existing_atom(collection_name) 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} = %{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), do: fetch_collection(collection_name, actor, page),
else: default_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], when visibility in [:public, :unlisted] and collection in [:outbox, :followers, :following],
do: true do: true
defp can_get_collection?(_collection_name, %Actor{} = actor, %Actor{} = actor_applicant), defp can_get_collection?(_collection_name, %Actor{} = actor, %Actor{} = actor_applicant) do
do: actor_applicant_group_member?(actor, actor_applicant) 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 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") File.read!("test/fixtures/mastodon-status-2.json")
|> Jason.decode!() |> Jason.decode!()
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
Mock Mock
|> expect(:call, fn |> expect(:call, 2, fn
%{method: :get, url: ^url}, _opts -> %{method: :get, url: ^url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}} {: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) end)
{:ok, object} = ActivityPub.fetch_object_from_url(url) {: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") File.read!("test/fixtures/mastodon-status-4.json")
|> Jason.decode!() |> Jason.decode!()
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
Mock Mock
|> expect(:call, 2, fn |> expect(:call, 3, fn
%{method: :get, url: ^url}, _opts -> %{method: :get, url: ^url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}} {:ok, %Tesla.Env{status: 200, body: data}}
%{method: :get, url: ^reply_to_url}, _opts -> %{method: :get, url: ^reply_to_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: reply_to_data}} {: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) end)
{:ok, object} = ActivityPub.fetch_object_from_url(url) {:ok, object} = ActivityPub.fetch_object_from_url(url)
@ -98,13 +112,26 @@ defmodule Mobilizon.Federation.ActivityPubTest do
File.read!("test/fixtures/peertube-video.json") File.read!("test/fixtures/peertube-video.json")
|> Jason.decode!() |> Jason.decode!()
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
Mock Mock
|> expect(:call, 2, fn |> expect(:call, 5, fn
%{method: :get, url: ^url}, _opts -> %{method: :get, url: ^url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}} {:ok, %Tesla.Env{status: 200, body: data}}
%{method: :get, url: ^origin_url}, _opts -> %{method: :get, url: ^origin_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: origin_data}} {: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) end)
{:ok, object} = ActivityPub.fetch_object_from_url(url) {:ok, object} = ActivityPub.fetch_object_from_url(url)

View file

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

View file

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

View file

@ -56,6 +56,19 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
end end
test "it fails for incoming deletes with spoofed origin" do 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) comment = insert(:comment)
announce_data = announce_data =
@ -77,7 +90,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
data data
|> Map.put("object", object) |> Map.put("object", object)
{:error, :unknown_actor} = Transmogrifier.handle_incoming(data) :error = Transmogrifier.handle_incoming(data)
assert Discussions.get_comment_from_url(comment.url) assert Discussions.get_comment_from_url(comment.url)
end end
@ -132,12 +145,12 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
|> Map.put("id", deleted_actor_url) |> Map.put("id", deleted_actor_url)
Mock Mock
|> expect(:call, fn |> expect(:call, 2, fn
%{url: ^deleted_actor_url}, _opts -> %{url: ^deleted_actor_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: deleted_actor_data}} {:ok, %Tesla.Env{status: 200, body: deleted_actor_data}}
end) end)
assert :error == Transmogrifier.handle_incoming(data) assert {:error, "Group object URL remote"} == Transmogrifier.handle_incoming(data)
assert Actors.get_actor_by_url(url) assert Actors.get_actor_by_url(url)
end end

View file

@ -1,16 +1,31 @@
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.FollowTest do defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.FollowTest do
use Mobilizon.DataCase use Mobilizon.DataCase
import Mox
import ExUnit.CaptureLog import ExUnit.CaptureLog
import Mobilizon.Factory import Mobilizon.Factory
alias Mobilizon.Actors alias Mobilizon.Actors
alias Mobilizon.Actors.Follower alias Mobilizon.Actors.Follower
alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Transmogrifier} alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Transmogrifier}
alias Mobilizon.Service.HTTP.ActivityPub.Mock
describe "handle incoming follow requests" do describe "handle incoming follow requests" do
test "it works only for groups" do test "it works only for groups" do
actor = insert(:actor) 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 = data =
File.read!("test/fixtures/mastodon-follow-activity.json") File.read!("test/fixtures/mastodon-follow-activity.json")
|> Jason.decode!() |> Jason.decode!()
@ -27,6 +42,20 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.FollowTest do
test "it works for incoming follow requests" do test "it works for incoming follow requests" do
actor = insert(:group) 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 = data =
File.read!("test/fixtures/mastodon-follow-activity.json") File.read!("test/fixtures/mastodon-follow-activity.json")
|> Jason.decode!() |> Jason.decode!()

View file

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

View file

@ -1,8 +1,8 @@
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UpdateTest do defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UpdateTest do
use Mobilizon.DataCase use Mobilizon.DataCase
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
use Oban.Testing, repo: Mobilizon.Storage.Repo use Oban.Testing, repo: Mobilizon.Storage.Repo
import Mobilizon.Factory import Mobilizon.Factory
import Mox
alias Mobilizon.{Actors, Events, Posts} alias Mobilizon.{Actors, Events, Posts}
alias Mobilizon.Actors.{Actor, Member} alias Mobilizon.Actors.{Actor, Member}
@ -10,79 +10,100 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UpdateTest do
alias Mobilizon.Posts.Post alias Mobilizon.Posts.Post
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier} alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier}
alias Mobilizon.Federation.ActivityStream.Convertible alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Service.HTTP.ActivityPub.Mock
describe "handle incoming update activities" do describe "handle incoming update activities" do
test "it works for incoming update activities on actors" 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) actor_data =
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!() File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
object = Mock
update_data["object"] |> expect(:call, 2, fn
|> Map.put("actor", data["actor"]) %{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
|> Map.put("id", data["actor"]) {:ok, %Tesla.Env{status: 200, body: actor_data}}
update_data = %{method: :get, url: "https://framapiaf.org/users/tcit"}, _opts ->
update_data {:ok, %Tesla.Env{status: 200, body: actor_data}}
|> Map.put("actor", data["actor"]) end)
|> Map.put("object", object)
{:ok, %Activity{data: _data, local: false}, _} = {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
Transmogrifier.handle_incoming(update_data) update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
{:ok, %Actor{} = actor} = Actors.get_actor_by_url(update_data["actor"]) object =
assert actor.name == "nextsoft" update_data["object"]
|> Map.put("actor", data["actor"])
|> Map.put("id", data["actor"])
assert actor.summary == "<p>Some bio</p>" update_data =
end 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 end
test "it works for incoming update activities on events" do 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}} = actor_data =
Transmogrifier.handle_incoming(data) File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
assert_enqueued( Mock
worker: Mobilizon.Service.Workers.BuildSearch, |> expect(:call, 2, fn
args: %{event_id: event_id, op: :insert_search_event} %{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 = assert_enqueued(
data["object"] worker: Mobilizon.Service.Workers.BuildSearch,
|> Map.put("actor", data["actor"]) args: %{event_id: event_id, op: :insert_search_event}
|> Map.put("name", "My updated event") )
|> Map.put("id", data["object"]["id"])
|> Map.put("type", "Event")
update_data = assert %{success: 1, snoozed: 0, failure: 0} == Oban.drain_queue(queue: :search)
update_data
|> Map.put("actor", data["actor"])
|> Map.put("object", object)
{:ok, %Activity{data: data, local: false}, _} = update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
Transmogrifier.handle_incoming(update_data)
%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( update_data =
worker: Mobilizon.Service.Workers.BuildSearch, update_data
args: %{event_id: event_id, op: :update_search_event} |> 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"] assert_enqueued(
end 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 end
# test "it works for incoming update activities which lock the account" do # 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 # Upstream: https://git.pleroma.social/pleroma/pleroma/blob/develop/test/web/activity_pub/transmogrifier_test.exs
defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
use Mobilizon.DataCase use Mobilizon.DataCase
use Oban.Testing, repo: Mobilizon.Storage.Repo use Oban.Testing, repo: Mobilizon.Storage.Repo
@ -33,54 +31,75 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
describe "handle incoming events" do describe "handle incoming events" do
test "it works for incoming events" do test "it works for incoming events" do
use_cassette "activity_pub/fetch_mobilizon_post_activity" do actor_data = File.read!("test/fixtures/mastodon-actor.json") |> Jason.decode!()
data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, %Event{} = event} = Mock
Transmogrifier.handle_incoming(data) |> 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://framapiaf.org/users/tcit" ->
"https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93/activity" 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!()
# {:ok, %Activity{data: data, local: false}, %Event{} = event} =
# assert data["cc"] == [ Transmogrifier.handle_incoming(data)
# "https://framapiaf.org/users/admin/followers",
# "http://localtesting.pleroma.lol/users/lain"
# ]
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"] == [ object = data["object"]
# "https://framapiaf.org/users/admin/followers",
# "http://localtesting.pleroma.lol/users/lain"
# ]
assert object["actor"] == "https://mobilizon.fr/@metacartes" assert object["id"] ==
assert object["location"]["name"] == "Locaux de Framasoft" "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93"
# assert object["attributedTo"] == "https://mobilizon.fr/@metacartes"
assert event.physical_address.street == "10 Rue Jangot" assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert event.physical_address.url == # assert object["cc"] == [
"https://event1.tcit.fr/address/eeecc11d-0030-43e8-a897-6422876372jd" # "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"]) assert event.physical_address.street == "10 Rue Jangot"
end
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 end
test "it works for incoming events from Gancio" do test "it works for incoming events from Gancio" do
data = File.read!("test/fixtures/gancio-event-activity.json") |> Jason.decode!() 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} = {:ok, %Activity{data: data, local: false}, %Event{} = event} =
Transmogrifier.handle_incoming(data) Transmogrifier.handle_incoming(data)
@ -524,6 +543,12 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
end end
test "it accepts incoming resources and handles group being not found" do 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 = creator =
insert(:actor, insert(:actor,
domain: "mobilizon.app", domain: "mobilizon.app",
@ -649,12 +674,18 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
test "it works for incoming announces" do test "it works for incoming announces" do
data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!() data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
status_data = File.read!("test/fixtures/mastodon-status.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 Mock
|> expect(:call, fn |> expect(:call, 2, fn
%{method: :get, url: "https://framapiaf.org/users/peertube/statuses/104584600044284729"}, %{method: :get, url: url}, _opts ->
_opts -> case url do
{:ok, %Tesla.Env{status: 200, body: status_data}} "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) end)
{:ok, _, %Comment{actor: %Actor{url: actor_url}, url: comment_url}} = {: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") File.read!("test/fixtures/mastodon-announce.json")
|> Jason.decode!() |> Jason.decode!()
|> Map.put("object", comment_url) |> Map.put("object", comment_url)
|> Map.put("actor", actor_url)
Mock Mock
|> expect(:call, fn |> expect(:call, fn
%{method: :get, url: ^actor_url}, _opts -> %{method: :get, url: ^actor_url}, _opts ->
actor_data = Map.put(actor_data, "id", actor_url)
{:ok, %Tesla.Env{status: 200, body: actor_data}} {:ok, %Tesla.Env{status: 200, body: actor_data}}
end) end)
@ -926,10 +959,21 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
File.read!("test/fixtures/https__info.pleroma.site_activity.json") File.read!("test/fixtures/https__info.pleroma.site_activity.json")
|> Jason.decode!() |> Jason.decode!()
actor_data =
File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!()
|> Map.put("id", "https://framapiaf.org/users/admin")
Mock Mock
|> expect(:call, fn |> expect(:call, 2, fn
%{method: :get, url: "https://info.pleroma.site/activity.json"}, _opts -> %{method: :get, url: url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: data}} 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) end)
data = %{ 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#",
"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",
"suspended": "toot:suspended",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
}
}
],
"id": "https://social.tcit.fr/users/tcit",
"type": "Person",
"following": "https://social.tcit.fr/users/tcit/following",
"followers": "https://social.tcit.fr/users/tcit/followers",
"inbox": "https://social.tcit.fr/users/tcit/inbox",
"outbox": "https://social.tcit.fr/users/tcit/outbox",
"featured": "https://social.tcit.fr/users/tcit/collections/featured",
"featuredTags": "https://social.tcit.fr/users/tcit/collections/tags",
"preferredUsername": "tcit",
"name": "🦄 Thomas Citharel",
"summary": "<p>Hoping to make people&apos;s life better with free software at <span class=\"h-card\"><a href=\"https://framapiaf.org/@Framasoft\" class=\"u-url mention\">@<span>Framasoft</span></a></span>.</p><p>ᕕ(ᐛ)ᕗ</p>",
"url": "https://social.tcit.fr/@tcit",
"manuallyApprovesFollowers": false,
"discoverable": true,
"published": "2017-04-03T00:00:00Z",
"devices": "https://social.tcit.fr/users/tcit/collections/devices",
"publicKey": {
"id": "https://social.tcit.fr/users/tcit#main-key",
"owner": "https://social.tcit.fr/users/tcit",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApXwYMUdFg3XUd+bGsh8C\nyiMRGpRGAWuCdM5pDWx5uM4pW2pM3xbHbcI21j9h8BmlAiPg6hbZD73KGly2N8Rt\n5iIS0I+l6i8kA1JCCdlAaDTRd41RKMggZDoQvjVZQtsyE1VzMeU2kbqqTFN6ew7H\nvbd6O0NhixoKoZ5f3jwuBDZoT0p1TAcaMdmG8oqHD97isizkDnRn8cOBA6wtI+xb\n5xP2zxZMsLpTDZLiKU8XcPKZCw4OfQfmDmKkHtrFb77jCAQj/s/FxjVnvxRwmfhN\nnWy0D+LUV/g63nHh/b5zXIeV92QZLvDYbgbezmzUzv9UeA1s70GGbaDqCIy85gw9\n+wIDAQAB\n-----END PUBLIC KEY-----\n"
},
"tag": [],
"attachment": [
{
"type": "PropertyValue",
"name": "Works at",
"value": "<span class=\"h-card\"><a href=\"https://framapiaf.org/@Framasoft\" class=\"u-url mention\">@<span>Framasoft@framapiaf.org</span></a></span>"
},
{
"type": "PropertyValue",
"name": "Pronouns",
"value": "He/Him"
},
{
"type": "PropertyValue",
"name": "Work Account",
"value": "<a href=\"https://framapiaf.org/@tcit\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">framapiaf.org/@tcit</span><span class=\"invisible\"></span></a>"
},
{
"type": "PropertyValue",
"name": "Site",
"value": "<a href=\"https://tcit.fr\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">tcit.fr</span><span class=\"invisible\"></span></a>"
}
],
"endpoints": {
"sharedInbox": "https://social.tcit.fr/inbox"
},
"icon": {
"type": "Image",
"mediaType": "image/jpeg",
"url": "https://social.tcit.fr/system/accounts/avatars/000/000/001/original/a28c50ce5f2b13fd.jpg"
},
"image": {
"type": "Image",
"mediaType": "image/jpeg",
"url": "https://social.tcit.fr/system/accounts/headers/000/000/001/original/ac9c4a71083bd9a1.jpg"
}
}

View file

@ -0,0 +1,112 @@
{
"@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"
}
}

55
test/fixtures/signature/nyu_rye.json vendored Normal file
View file

@ -0,0 +1,55 @@
{
"@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" }
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,20 +1,19 @@
defmodule Mobilizon.ActorsTest do defmodule Mobilizon.ActorsTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
use Mobilizon.DataCase use Mobilizon.DataCase
use Oban.Testing, repo: Mobilizon.Storage.Repo use Oban.Testing, repo: Mobilizon.Storage.Repo
import Mox
import Mobilizon.Factory import Mobilizon.Factory
alias Mobilizon.{Actors, Config, Discussions, Events, Users} alias Mobilizon.{Actors, Config, Discussions, Events, Users}
alias Mobilizon.Actors.{Actor, Bot, Follower, Member} alias Mobilizon.Actors.{Actor, Bot, Follower, Member}
alias Mobilizon.Discussions.Comment alias Mobilizon.Discussions.Comment
alias Mobilizon.Events.Event alias Mobilizon.Events.Event
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Medias.File, as: FileModel alias Mobilizon.Medias.File, as: FileModel
alias Mobilizon.Service.HTTP.ActivityPub.Mock
alias Mobilizon.Service.Workers alias Mobilizon.Service.Workers
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Web.Upload.Uploader alias Mobilizon.Web.Upload.Uploader
describe "actors" do describe "actors" do
@ -99,25 +98,32 @@ defmodule Mobilizon.ActorsTest do
end end
test "get_actor_by_name/1 returns a remote actor" do test "get_actor_by_name/1 returns a remote actor" do
use_cassette "actors/remote_actor_mastodon_tcit" do tcit_social_tcit =
{:ok, "test/fixtures/mastodon-tcit-tcit.json" |> File.read!() |> Jason.decode!()
%Actor{
id: actor_id,
preferred_username: preferred_username,
domain: domain,
avatar: %FileModel{name: picture_name} = _picture
} = _actor} = ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url)
assert picture_name == "a28c50ce5f2b13fd.jpg" Mock
|> expect(:call, fn
%{method: :get, url: @remote_account_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: tcit_social_tcit}}
end)
%Actor{ {:ok,
id: actor_found_id, %Actor{
avatar: %FileModel{name: picture_name} = _picture id: actor_id,
} = Actors.get_actor_by_name("#{preferred_username}@#{domain}") preferred_username: preferred_username,
domain: domain,
avatar: %FileModel{name: picture_name} = _picture
} = _actor} = ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url)
assert actor_found_id == actor_id assert picture_name == "a28c50ce5f2b13fd.jpg"
assert picture_name == "a28c50ce5f2b13fd.jpg"
end %Actor{
id: actor_found_id,
avatar: %FileModel{name: picture_name} = _picture
} = Actors.get_actor_by_name("#{preferred_username}@#{domain}")
assert actor_found_id == actor_id
assert picture_name == "a28c50ce5f2b13fd.jpg"
end end
test "get_local_actor_by_name_with_preload!/1 returns the local actor with its organized events", test "get_local_actor_by_name_with_preload!/1 returns the local actor with its organized events",
@ -155,22 +161,29 @@ defmodule Mobilizon.ActorsTest do
end end
test "get_actor_by_name_with_preload!/1 returns the remote actor with its organized events" do test "get_actor_by_name_with_preload!/1 returns the remote actor with its organized events" do
use_cassette "actors/remote_actor_mastodon_tcit" do tcit_social_tcit =
with {:ok, %Actor{} = actor} <- "test/fixtures/mastodon-tcit-tcit.json" |> File.read!() |> Jason.decode!()
ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url) do
assert Actors.get_actor_by_name_with_preload(
"#{actor.preferred_username}@#{actor.domain}"
).organized_events == []
event = insert(:event, organizer_actor: actor) Mock
|> expect(:call, fn
%{method: :get, url: @remote_account_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: tcit_social_tcit}}
end)
event_found_id = with {:ok, %Actor{} = actor} <-
Actors.get_actor_by_name_with_preload("#{actor.preferred_username}@#{actor.domain}").organized_events ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url) do
|> hd assert Actors.get_actor_by_name_with_preload(
|> Map.get(:id) "#{actor.preferred_username}@#{actor.domain}"
).organized_events == []
assert event_found_id == event.id event = insert(:event, organizer_actor: actor)
end
event_found_id =
Actors.get_actor_by_name_with_preload("#{actor.preferred_username}@#{actor.domain}").organized_events
|> hd
|> Map.get(:id)
assert event_found_id == event.id
end end
end end
@ -185,19 +198,26 @@ defmodule Mobilizon.ActorsTest do
test "test search_actors/4 returns actors with similar usernames", test "test search_actors/4 returns actors with similar usernames",
%{actor: %Actor{id: actor_id}} do %{actor: %Actor{id: actor_id}} do
use_cassette "actors/remote_actor_mastodon_tcit" do tcit_social_tcit =
with {:ok, %Actor{id: actor2_id}} <- "test/fixtures/mastodon-tcit-tcit.json" |> File.read!() |> Jason.decode!()
ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url) do
%Page{total: 2, elements: actors} =
Actors.search_actors("tcit",
actor_type: :Person,
minimum_visibility: :private
)
actors_ids = actors |> Enum.map(& &1.id) Mock
|> expect(:call, fn
%{method: :get, url: @remote_account_url}, _opts ->
{:ok, %Tesla.Env{status: 200, body: tcit_social_tcit}}
end)
assert MapSet.new(actors_ids) == MapSet.new([actor2_id, actor_id]) with {:ok, %Actor{id: actor2_id}} <-
end ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url) do
%Page{total: 2, elements: actors} =
Actors.search_actors("tcit",
actor_type: :Person,
minimum_visibility: :private
)
actors_ids = actors |> Enum.map(& &1.id)
assert MapSet.new(actors_ids) == MapSet.new([actor2_id, actor_id])
end end
end end

View file

@ -120,7 +120,6 @@ defmodule Mobilizon.Web.ActivityPubControllerTest do
describe "/@:preferred_username/inbox" do describe "/@:preferred_username/inbox" do
test "it inserts an incoming event into the database", %{conn: conn} do test "it inserts an incoming event into the database", %{conn: conn} do
# use_cassette "activity_pub_controller/mastodon-post-activity_actor_call" do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!() data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
%Actor{url: remote_actor_url} = %Actor{url: remote_actor_url} =
@ -151,6 +150,9 @@ defmodule Mobilizon.Web.ActivityPubControllerTest do
^local_actor_url -> ^local_actor_url ->
{:ok, %Tesla.Env{status: 200, body: local_actor_data}} {:ok, %Tesla.Env{status: 200, body: local_actor_data}}
"https://framapiaf.org/users/tcit" ->
{:ok, %Tesla.Env{status: 404, body: ""}}
"https://framapiaf.org/users/admin/statuses/99512778738411822" -> "https://framapiaf.org/users/admin/statuses/99512778738411822" ->
{:ok, %Tesla.Env{status: 404, body: ""}} {:ok, %Tesla.Env{status: 404, body: ""}}
end end
@ -168,7 +170,6 @@ defmodule Mobilizon.Web.ActivityPubControllerTest do
ActivityPub.fetch_object_from_url(data["object"]["id"]) ActivityPub.fetch_object_from_url(data["object"]["id"])
assert comment.actor.id == remote_actor.id assert comment.actor.id == remote_actor.id
# end
end end
end end

View file

@ -5,8 +5,9 @@
defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentityTest do defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentityTest do
use Mobilizon.Web.ConnCase use Mobilizon.Web.ConnCase
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney import Mox
alias Mobilizon.Service.HTTP.ActivityPub.Mock
alias Mobilizon.Web.Plugs.MappedSignatureToIdentity alias Mobilizon.Web.Plugs.MappedSignatureToIdentity
defp set_signature(conn, key_id) do defp set_signature(conn, key_id) do
@ -15,47 +16,100 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentityTest do
|> assign(:valid_signature, true) |> assign(:valid_signature, true)
end end
test "it successfully maps a valid identity with a valid signature" do defp framapiaf_admin do
use_cassette "activity_pub/signature/valid" do "test/fixtures/signature/framapiaf_admin.json"
conn = |> File.read!()
build_conn(:get, "/doesntmattter") |> Jason.decode!()
|> set_signature("https://framapiaf.org/users/admin") end
|> MappedSignatureToIdentity.call(%{})
refute is_nil(conn.assigns.actor) defp nyu_rye do
end "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)
conn =
build_conn(:get, "/doesntmattter")
|> set_signature("https://framapiaf.org/users/admin")
|> MappedSignatureToIdentity.call(%{})
refute is_nil(conn.assigns.actor)
end end
test "it successfully maps a valid identity with a valid signature with payload" do test "it successfully maps a valid identity with a valid signature with payload" do
use_cassette "activity_pub/signature/valid_payload" do Mock
conn = |> expect(:call, fn
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"}) %{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
|> set_signature("https://framapiaf.org/users/admin") {:ok, %Tesla.Env{status: 200, body: framapiaf_admin()}}
|> MappedSignatureToIdentity.call(%{}) end)
refute is_nil(conn.assigns.actor) Mock
end |> expect(:call, fn
%{method: :post, url: "/doesntmattter"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: ""}}
end)
conn =
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
|> set_signature("https://framapiaf.org/users/admin")
|> MappedSignatureToIdentity.call(%{})
refute is_nil(conn.assigns.actor)
end end
test "it considers a mapped identity to be invalid when it mismatches a payload" do test "it considers a mapped identity to be invalid when it mismatches a payload" do
use_cassette "activity_pub/signature/invalid_payload" do Mock
conn = |> expect(:call, fn
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"}) %{method: :get, url: "https://niu.moe/users/rye"}, _opts ->
|> set_signature("https://niu.moe/users/rye") {:ok, %Tesla.Env{status: 200, body: nyu_rye()}}
|> MappedSignatureToIdentity.call(%{}) end)
assert %{valid_signature: false} == conn.assigns Mock
end |> expect(:call, fn
%{method: :post, url: "/doesntmattter"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: ""}}
end)
conn =
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
|> set_signature("https://niu.moe/users/rye")
|> MappedSignatureToIdentity.call(%{})
assert %{valid_signature: false} == conn.assigns
end 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 test "it considers a mapped identity to be invalid when the identity cannot be found" do
use_cassette "activity_pub/signature/invalid_not_found" do Mock
conn = |> expect(:call, fn
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"}) %{method: :get, url: "https://mastodon.social/users/gargron"}, _opts ->
|> set_signature("https://mastodon.social/users/gargron") {:ok, %Tesla.Env{status: 404, body: ""}}
|> MappedSignatureToIdentity.call(%{}) end)
assert %{valid_signature: false} == conn.assigns Mock
end |> expect(:call, fn
%{method: :post, url: "/doesntmattter"}, _opts ->
{:ok, %Tesla.Env{status: 200, body: ""}}
end)
conn =
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
|> set_signature("https://mastodon.social/users/gargron")
|> MappedSignatureToIdentity.call(%{})
assert %{valid_signature: false} == conn.assigns
end end
end end