Improve group refreshment and fixed date signature generation
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
6d599441a9
commit
55af776df9
@ -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: [
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
_ ->
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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!()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
36
test/fixtures/gancio-actor.json
vendored
Normal 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
119
test/fixtures/mastodon-tcit-tcit.json
vendored
Normal 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'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"
|
||||
}
|
||||
}
|
112
test/fixtures/signature/framapiaf_admin.json
vendored
Normal file
112
test/fixtures/signature/framapiaf_admin.json
vendored
Normal 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'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
55
test/fixtures/signature/nyu_rye.json
vendored
Normal 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
@ -1,20 +1,19 @@
|
||||
defmodule Mobilizon.ActorsTest do
|
||||
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
||||
use Mobilizon.DataCase
|
||||
use Oban.Testing, repo: Mobilizon.Storage.Repo
|
||||
|
||||
import Mox
|
||||
import Mobilizon.Factory
|
||||
|
||||
alias Mobilizon.{Actors, Config, Discussions, Events, Users}
|
||||
alias Mobilizon.Actors.{Actor, Bot, Follower, Member}
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
alias Mobilizon.Medias.File, as: FileModel
|
||||
alias Mobilizon.Service.HTTP.ActivityPub.Mock
|
||||
alias Mobilizon.Service.Workers
|
||||
alias Mobilizon.Storage.Page
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
|
||||
alias Mobilizon.Web.Upload.Uploader
|
||||
|
||||
describe "actors" do
|
||||
@ -99,25 +98,32 @@ defmodule Mobilizon.ActorsTest do
|
||||
end
|
||||
|
||||
test "get_actor_by_name/1 returns a remote actor" do
|
||||
use_cassette "actors/remote_actor_mastodon_tcit" do
|
||||
{:ok,
|
||||
%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)
|
||||
tcit_social_tcit =
|
||||
"test/fixtures/mastodon-tcit-tcit.json" |> File.read!() |> Jason.decode!()
|
||||
|
||||
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{
|
||||
id: actor_found_id,
|
||||
avatar: %FileModel{name: picture_name} = _picture
|
||||
} = Actors.get_actor_by_name("#{preferred_username}@#{domain}")
|
||||
{:ok,
|
||||
%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 actor_found_id == actor_id
|
||||
assert picture_name == "a28c50ce5f2b13fd.jpg"
|
||||
end
|
||||
assert picture_name == "a28c50ce5f2b13fd.jpg"
|
||||
|
||||
%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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
with {:ok, %Actor{} = actor} <-
|
||||
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 == []
|
||||
tcit_social_tcit =
|
||||
"test/fixtures/mastodon-tcit-tcit.json" |> File.read!() |> Jason.decode!()
|
||||
|
||||
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 =
|
||||
Actors.get_actor_by_name_with_preload("#{actor.preferred_username}@#{actor.domain}").organized_events
|
||||
|> hd
|
||||
|> Map.get(:id)
|
||||
with {:ok, %Actor{} = actor} <-
|
||||
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 == []
|
||||
|
||||
assert event_found_id == event.id
|
||||
end
|
||||
event = insert(:event, organizer_actor: actor)
|
||||
|
||||
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
|
||||
|
||||
@ -185,19 +198,26 @@ defmodule Mobilizon.ActorsTest do
|
||||
|
||||
test "test search_actors/4 returns actors with similar usernames",
|
||||
%{actor: %Actor{id: actor_id}} do
|
||||
use_cassette "actors/remote_actor_mastodon_tcit" do
|
||||
with {:ok, %Actor{id: actor2_id}} <-
|
||||
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
|
||||
)
|
||||
tcit_social_tcit =
|
||||
"test/fixtures/mastodon-tcit-tcit.json" |> File.read!() |> Jason.decode!()
|
||||
|
||||
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])
|
||||
end
|
||||
with {:ok, %Actor{id: actor2_id}} <-
|
||||
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
|
||||
|
||||
|
@ -120,7 +120,6 @@ defmodule Mobilizon.Web.ActivityPubControllerTest do
|
||||
|
||||
describe "/@:preferred_username/inbox" 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!()
|
||||
|
||||
%Actor{url: remote_actor_url} =
|
||||
@ -151,6 +150,9 @@ defmodule Mobilizon.Web.ActivityPubControllerTest do
|
||||
^local_actor_url ->
|
||||
{: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" ->
|
||||
{:ok, %Tesla.Env{status: 404, body: ""}}
|
||||
end
|
||||
@ -168,7 +170,6 @@ defmodule Mobilizon.Web.ActivityPubControllerTest do
|
||||
ActivityPub.fetch_object_from_url(data["object"]["id"])
|
||||
|
||||
assert comment.actor.id == remote_actor.id
|
||||
# end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -5,8 +5,9 @@
|
||||
|
||||
defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentityTest do
|
||||
use Mobilizon.Web.ConnCase
|
||||
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
||||
import Mox
|
||||
|
||||
alias Mobilizon.Service.HTTP.ActivityPub.Mock
|
||||
alias Mobilizon.Web.Plugs.MappedSignatureToIdentity
|
||||
|
||||
defp set_signature(conn, key_id) do
|
||||
@ -15,47 +16,100 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentityTest do
|
||||
|> assign(:valid_signature, true)
|
||||
end
|
||||
|
||||
test "it successfully maps a valid identity with a valid signature" do
|
||||
use_cassette "activity_pub/signature/valid" do
|
||||
conn =
|
||||
build_conn(:get, "/doesntmattter")
|
||||
|> set_signature("https://framapiaf.org/users/admin")
|
||||
|> MappedSignatureToIdentity.call(%{})
|
||||
defp framapiaf_admin do
|
||||
"test/fixtures/signature/framapiaf_admin.json"
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
end
|
||||
|
||||
refute is_nil(conn.assigns.actor)
|
||||
end
|
||||
defp nyu_rye do
|
||||
"test/fixtures/signature/nyu_rye.json"
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
end
|
||||
|
||||
test "it successfully maps a valid identity with a valid signature" do
|
||||
Mock
|
||||
|> expect(:call, fn
|
||||
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
|
||||
{:ok, %Tesla.Env{status: 200, body: framapiaf_admin()}}
|
||||
end)
|
||||
|
||||
Mock
|
||||
|> expect(:call, fn
|
||||
%{method: :get, url: "/doesntmattter"}, _opts ->
|
||||
{:ok, %Tesla.Env{status: 200, body: ""}}
|
||||
end)
|
||||
|
||||
conn =
|
||||
build_conn(:get, "/doesntmattter")
|
||||
|> set_signature("https://framapiaf.org/users/admin")
|
||||
|> MappedSignatureToIdentity.call(%{})
|
||||
|
||||
refute is_nil(conn.assigns.actor)
|
||||
end
|
||||
|
||||
test "it successfully maps a valid identity with a valid signature with payload" do
|
||||
use_cassette "activity_pub/signature/valid_payload" do
|
||||
conn =
|
||||
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
|
||||
|> set_signature("https://framapiaf.org/users/admin")
|
||||
|> MappedSignatureToIdentity.call(%{})
|
||||
Mock
|
||||
|> expect(:call, fn
|
||||
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
|
||||
{:ok, %Tesla.Env{status: 200, body: framapiaf_admin()}}
|
||||
end)
|
||||
|
||||
refute is_nil(conn.assigns.actor)
|
||||
end
|
||||
Mock
|
||||
|> 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
|
||||
|
||||
test "it considers a mapped identity to be invalid when it mismatches a payload" do
|
||||
use_cassette "activity_pub/signature/invalid_payload" do
|
||||
conn =
|
||||
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
|
||||
|> set_signature("https://niu.moe/users/rye")
|
||||
|> MappedSignatureToIdentity.call(%{})
|
||||
Mock
|
||||
|> expect(:call, fn
|
||||
%{method: :get, url: "https://niu.moe/users/rye"}, _opts ->
|
||||
{:ok, %Tesla.Env{status: 200, body: nyu_rye()}}
|
||||
end)
|
||||
|
||||
assert %{valid_signature: false} == conn.assigns
|
||||
end
|
||||
Mock
|
||||
|> 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
|
||||
|
||||
@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
|
||||
use_cassette "activity_pub/signature/invalid_not_found" do
|
||||
conn =
|
||||
build_conn(:post, "/doesntmattter", %{"actor" => "https://framapiaf.org/users/admin"})
|
||||
|> set_signature("https://mastodon.social/users/gargron")
|
||||
|> MappedSignatureToIdentity.call(%{})
|
||||
Mock
|
||||
|> expect(:call, fn
|
||||
%{method: :get, url: "https://mastodon.social/users/gargron"}, _opts ->
|
||||
{:ok, %Tesla.Env{status: 404, body: ""}}
|
||||
end)
|
||||
|
||||
assert %{valid_signature: false} == conn.assigns
|
||||
end
|
||||
Mock
|
||||
|> 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
|
||||
|
Loading…
Reference in New Issue
Block a user