diff --git a/changelog.d/fedidev-federation-compat.add b/changelog.d/fedidev-federation-compat.add new file mode 100644 index 000000000..bb29925f9 --- /dev/null +++ b/changelog.d/fedidev-federation-compat.add @@ -0,0 +1 @@ +Accept more real-world ActivityPub object shapes collected by fedidev.fun diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index b90d68636..c4cc1f014 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -79,9 +79,11 @@ defmodule Pleroma.Object.Containment do def contain_origin(id, %{"actor" => _actor} = params) do id_uri = URI.parse(id) - actor_uri = URI.parse(get_actor(params)) - compare_uris(actor_uri, id_uri) + case get_actor(params) do + actor when is_binary(actor) -> compare_uris(URI.parse(actor), id_uri) + _ -> :error + end end def contain_origin(id, %{"attributedTo" => actor} = params), diff --git a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex index cdab8d78d..0659c93d7 100644 --- a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex @@ -12,6 +12,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do require Pleroma.Constants @primary_key false + @tag_types ~w[Mention Hashtag Emoji Link] + embedded_schema do # Common field(:type, :string) @@ -49,7 +51,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do |> validate_required([:type, :href]) end - def changeset(struct, %{"type" => "Hashtag", "name" => name} = data) do + def changeset(struct, %{"type" => "Hashtag", "name" => name} = data) when is_binary(name) do name = String.downcase(name) data = Map.put(data, "name", name) @@ -58,7 +60,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do |> validate_required([:type, :name]) end - def changeset(struct, %{"type" => "Emoji"} = data) do + def changeset(struct, %{"type" => "Hashtag"} = data) do + struct + |> cast(data, []) + |> Map.put(:action, :ignore) + end + + def changeset(struct, %{"type" => "Emoji", "name" => name} = data) when is_binary(name) do data = data |> Map.put("name", String.trim(data["name"], ":")) @@ -84,6 +92,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do end end + def changeset(struct, %{"type" => "Emoji"} = data) do + struct + |> cast(data, []) + |> Map.put(:action, :ignore) + end + def changeset(struct, %{"type" => "Link"} = data) do struct |> cast(data, [:type, :name, :mediaType, :href]) @@ -91,6 +105,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do |> validate_required([:type, :href, :mediaType]) end + def changeset(struct, %{"type" => types} = data) when is_list(types) do + changeset(struct, infer_type(data)) + end + def changeset(struct, %{"type" => _} = data) do struct |> cast(data, []) @@ -98,7 +116,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do end def changeset(struct, data) when is_map(data) do - data = infer_type(data) + data = normalize(data) if Map.has_key?(data, "type") do changeset(struct, data) @@ -109,6 +127,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do end end + def normalize(data) when is_map(data), do: infer_type(data) + + defp infer_type(%{"type" => types} = data) when is_list(types) do + type = Enum.find(types, &(&1 in @tag_types)) || Enum.find(types, &is_binary/1) + + if type, do: Map.put(data, "type", type), else: Map.delete(data, "type") + end + defp infer_type(%{"type" => _} = data), do: data defp infer_type(%{"name" => "#" <> _} = data), do: Map.put(data, "type", "Hashtag") defp infer_type(%{"name" => "@" <> _} = data), do: Map.put(data, "type", "Mention") diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 61c585daa..33ab62ca3 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility @@ -28,6 +29,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do require Pleroma.Constants + @supported_types Pleroma.Constants.activity_types() ++ + Pleroma.Constants.object_types() ++ Pleroma.Constants.actor_types() + @doc """ Modifies an incoming AP object (mastodon format) to our internal format. """ @@ -301,58 +305,66 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do attachments = - Enum.map(attachment, fn data -> - url = - cond do - is_list(data["url"]) -> List.first(data["url"]) - is_map(data["url"]) -> data["url"] - true -> nil - end + Enum.map(attachment, fn + data when is_map(data) -> + url = + cond do + is_list(data["url"]) -> List.first(data["url"]) + is_map(data["url"]) -> data["url"] + true -> nil + end - media_type = - cond do - is_map(url) && is_bitstring(url["mediaType"]) && - url["mediaType"] =~ Pleroma.Constants.mime_regex() -> - url["mediaType"] + url_data = if is_map(url), do: url, else: %{} - is_bitstring(data["mediaType"]) && data["mediaType"] =~ Pleroma.Constants.mime_regex() -> - data["mediaType"] + media_type = + cond do + is_bitstring(url_data["mediaType"]) && + url_data["mediaType"] =~ Pleroma.Constants.mime_regex() -> + url_data["mediaType"] - is_bitstring(data["mimeType"]) && data["mimeType"] =~ Pleroma.Constants.mime_regex() -> - data["mimeType"] + is_bitstring(data["mediaType"]) && + data["mediaType"] =~ Pleroma.Constants.mime_regex() -> + data["mediaType"] - true -> - nil - end + is_bitstring(data["mimeType"]) && data["mimeType"] =~ Pleroma.Constants.mime_regex() -> + data["mimeType"] - href = - cond do - is_map(url) && is_binary(url["href"]) -> url["href"] - is_binary(data["url"]) -> data["url"] - is_binary(data["href"]) -> data["href"] - true -> nil - end + true -> + nil + end + + href = + cond do + is_binary(url) -> url + is_binary(url_data["href"]) -> url_data["href"] + is_binary(data["url"]) -> data["url"] + is_binary(data["href"]) -> data["href"] + true -> nil + end + + if href && valid_http_url?(href) do + attachment_url = + %{ + "href" => href, + "type" => Map.get(url_data, "type", "Link") + } + |> Maps.put_if_present("mediaType", media_type) + |> Maps.put_if_present("width", url_data["width"] || data["width"]) + |> Maps.put_if_present("height", url_data["height"] || data["height"]) - if href && valid_http_url?(href) do - attachment_url = %{ - "href" => href, - "type" => Map.get(url || %{}, "type", "Link") + "url" => [attachment_url], + "type" => data["type"] || "Document" } |> Maps.put_if_present("mediaType", media_type) - |> Maps.put_if_present("width", (url || %{})["width"] || data["width"]) - |> Maps.put_if_present("height", (url || %{})["height"] || data["height"]) + |> Maps.put_if_present("name", data["name"]) + |> Maps.put_if_present("blurhash", data["blurhash"]) + else + nil + end - %{ - "url" => [attachment_url], - "type" => data["type"] || "Document" - } - |> Maps.put_if_present("mediaType", media_type) - |> Maps.put_if_present("name", data["name"]) - |> Maps.put_if_present("blurhash", data["blurhash"]) - else + _ -> nil - end end) |> Enum.filter(& &1) @@ -404,14 +416,23 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do defp emoji_icon_url(_), do: nil + defp valid_emoji_icon_url(icon) do + case emoji_icon_url(icon) do + url when is_binary(url) -> if valid_http_url?(url), do: url + _ -> nil + end + end + def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do emoji = tags - |> Enum.filter(fn data -> is_map(data) and data["type"] == "Emoji" and data["icon"] end) + |> Enum.filter(fn data -> + is_map(data) and data["type"] == "Emoji" and is_binary(data["name"]) and data["icon"] + end) |> Enum.reduce(%{}, fn data, mapping -> name = String.trim(data["name"], ":") - case emoji_icon_url(data["icon"]) do + case valid_emoji_icon_url(data["icon"]) do url when is_binary(url) -> Map.put(mapping, name, url) _ -> mapping end @@ -420,10 +441,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do Map.put(object, "emoji", emoji) end - def fix_emoji(%{"tag" => %{"type" => "Emoji"} = tag} = object) do - name = String.trim(tag["name"], ":") + def fix_emoji(%{"tag" => %{"type" => "Emoji", "name" => name} = tag} = object) + when is_binary(name) do + name = String.trim(name, ":") - with url when is_binary(url) <- emoji_icon_url(tag["icon"]) do + with url when is_binary(url) <- valid_emoji_icon_url(tag["icon"]) do Map.put(object, "emoji", %{name => url}) else _ -> object @@ -435,7 +457,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_tag(%{"tag" => tag} = object) when is_list(tag) do tags = tag - |> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end) + |> Enum.filter(fn data -> + is_map(data) and data["type"] == "Hashtag" and is_binary(data["name"]) + end) |> Enum.map(fn %{"name" => "#" <> hashtag} -> String.downcase(hashtag) %{"name" => hashtag} -> String.downcase(hashtag) @@ -502,10 +526,32 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> handle_incoming_normalized(options) end - defp fix_type_as_string(%{"type" => [type | _]} = data) when is_binary(type), - do: Map.put(data, "type", type) + defp fix_type_as_string(data) do + data + |> normalize_type() + |> normalize_tag_types() + end - defp fix_type_as_string(data), do: data + defp normalize_type(%{"type" => types} = data) when is_list(types) do + type = Enum.find(types, &(&1 in @supported_types)) || Enum.find(types, &is_binary/1) + + if type, do: Map.put(data, "type", type), else: data + end + + defp normalize_type(data), do: data + + defp normalize_tag_types(%{"tag" => tags} = data) when is_list(tags) do + Map.put(data, "tag", Enum.map(tags, &normalize_tag_type/1)) + end + + defp normalize_tag_types(%{"tag" => tag} = data) when is_map(tag) do + Map.put(data, "tag", normalize_tag_type(tag)) + end + + defp normalize_tag_types(data), do: data + + defp normalize_tag_type(tag) when is_map(tag), do: TagValidator.normalize(tag) + defp normalize_tag_type(tag), do: tag # Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them # with nil ID. diff --git a/test/pleroma/object/containment_test.exs b/test/pleroma/object/containment_test.exs index cf906ef47..5560dfce5 100644 --- a/test/pleroma/object/containment_test.exs +++ b/test/pleroma/object/containment_test.exs @@ -27,6 +27,11 @@ defmodule Pleroma.Object.ContainmentTest do }) end + test "rejects an empty actor list without crashing" do + assert :error == + Containment.contain_origin("https://example.com/objects/1", %{"actor" => []}) + end + test "contain_origin_from_id() catches obvious spoofing attempts" do data = %{ "id" => "http://example.com/~alyssa/activities/1234.json" diff --git a/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_attachments_test.exs b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_attachments_test.exs index a13912b08..9d15974fc 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_attachments_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_attachments_test.exs @@ -36,8 +36,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunAttachmentsTest do end end - defp ingest_fixture!(rel_path) do - data = rel_path |> File.read!() |> Jason.decode!() + defp ingest_data!(data) do ensure_remote_actor!(data["actor"]) assert {:ok, activity} = Transmogrifier.handle_incoming(data) @@ -46,6 +45,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunAttachmentsTest do {activity, object} end + defp ingest_fixture!(rel_path) do + rel_path + |> File.read!() + |> Jason.decode!() + |> ingest_data!() + end + test "drops non-http(s) attachment href" do {_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/attachment_payto_link.json") assert object.data["attachment"] == [] @@ -66,4 +72,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunAttachmentsTest do assert href == "https://fedidev.fun/images/007.png" end + + test "drops malformed attachment list entries" do + data = File.read!("test/fixtures/fedidev.fun/attachment_url_list.json") |> Jason.decode!() + data = update_in(data, ["object", "attachment"], &["malformed" | List.wrap(&1)]) + + {_activity, object} = ingest_data!(data) + + assert length(object.data["attachment"]) == 1 + end + + test "accepts a URI string in an attachment url list" do + data = File.read!("test/fixtures/fedidev.fun/attachment_url_list.json") |> Jason.decode!() + + data = + put_in(data, ["object", "attachment", "url"], [ + "https://fedidev.fun/images/007.png" + ]) + + {_activity, object} = ingest_data!(data) + + assert [%{"url" => [%{"href" => "https://fedidev.fun/images/007.png"}]}] = + object.data["attachment"] + end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_emoji_test.exs b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_emoji_test.exs index 56c305352..62e30f5af 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_emoji_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_emoji_test.exs @@ -36,8 +36,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunEmojiTest do end end - defp ingest_fixture!(rel_path) do - data = rel_path |> File.read!() |> Jason.decode!() + defp ingest_data!(data) do ensure_remote_actor!(data["actor"]) assert {:ok, activity} = Transmogrifier.handle_incoming(data) @@ -46,6 +45,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunEmojiTest do {activity, object} end + defp ingest_fixture!(rel_path) do + rel_path + |> File.read!() + |> Jason.decode!() + |> ingest_data!() + end + test "ingests Emoji tag with string icon" do {_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/emoji_icon_string.json") assert object.data["emoji"]["cow"] == "https://fedidev.fun/static/cow.png" @@ -62,4 +68,36 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunEmojiTest do {_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/emoji_missing_icon.json") assert object.data["emoji"] == %{} end + + test "ignores Emoji tag missing a string name" do + data = File.read!("test/fixtures/fedidev.fun/emoji_icon_string.json") |> Jason.decode!() + data = put_in(data, ["object", "tag", Access.at(0), "name"], nil) + + {_activity, object} = ingest_data!(data) + + assert object.data["emoji"] == %{} + end + + test "ignores Emoji tag with a non-http icon" do + data = File.read!("test/fixtures/fedidev.fun/emoji_icon_string.json") |> Jason.decode!() + data = put_in(data, ["object", "tag", Access.at(0), "icon"], "file:///tmp/cow.png") + + {_activity, object} = ingest_data!(data) + + assert object.data["emoji"] == %{} + end + + test "normalizes a list-typed Emoji tag before building the emoji map" do + data = File.read!("test/fixtures/fedidev.fun/emoji_icon_string.json") |> Jason.decode!() + + data = + put_in(data, ["object", "tag", Access.at(0), "type"], [ + "https://example.com/ns#CustomTag", + "Emoji" + ]) + + {_activity, object} = ingest_data!(data) + + assert object.data["emoji"]["cow"] == "https://fedidev.fun/static/cow.png" + end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_object_type_list_test.exs b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_object_type_list_test.exs index 619e6f477..b169dc181 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_object_type_list_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_object_type_list_test.exs @@ -50,4 +50,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunObjectTypeListTest do {_activity, object} = ingest_fixture!("test/fixtures/fedidev.fun/object_type_list.json") assert object.data["type"] == "Note" end + + test "selects a supported type after an extension type" do + data = File.read!("test/fixtures/fedidev.fun/object_type_list.json") |> Jason.decode!() + data = put_in(data, ["object", "type"], ["https://example.com/ns#CustomType", "Note"]) + ensure_remote_actor!(data["actor"]) + + assert {:ok, activity} = Transmogrifier.handle_incoming(data) + object = Object.normalize(activity.data["object"], fetch: false) + + assert object.data["type"] == "Note" + end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_tag_inference_test.exs b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_tag_inference_test.exs index ef7feb8ad..0650dd38c 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_tag_inference_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/fedidev_fun_tag_inference_test.exs @@ -62,4 +62,51 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FedidevFunTagInferenceTest do _ -> false end) end + + test "normalizes a list-typed tag type" do + data = + File.read!("test/fixtures/fedidev.fun/tag_hashtag_missing_type.json") |> Jason.decode!() + + data = put_in(data, ["object", "tag", Access.at(0), "type"], ["Hashtag"]) + ensure_remote_actor!(data["actor"]) + + assert {:ok, activity} = Transmogrifier.handle_incoming(data) + object = Object.normalize(activity.data["object"], fetch: false) + + assert "test" in Object.hashtags(object) + end + + test "ignores a list-typed Hashtag without a string name" do + data = + File.read!("test/fixtures/fedidev.fun/tag_hashtag_missing_type.json") |> Jason.decode!() + + data = put_in(data, ["object", "tag", Access.at(0), "type"], ["Hashtag"]) + data = put_in(data, ["object", "tag", Access.at(0), "name"], nil) + ensure_remote_actor!(data["actor"]) + + assert {:ok, activity} = Transmogrifier.handle_incoming(data) + object = Object.normalize(activity.data["object"], fetch: false) + + assert Object.hashtags(object) == [] + end + + test "infers a Link before deriving quoteUrl" do + data = + File.read!("test/fixtures/fedidev.fun/tag_hashtag_missing_type.json") |> Jason.decode!() + + data = + put_in(data, ["object", "tag"], [ + %{ + "href" => "https://fedidev.fun/assets/note1.jsonap", + "mediaType" => "application/activity+json" + } + ]) + + ensure_remote_actor!(data["actor"]) + + assert {:ok, activity} = Transmogrifier.handle_incoming(data) + object = Object.normalize(activity.data["object"], fetch: false) + + assert object.data["quoteUrl"] == "https://fedidev.fun/assets/note1.jsonap" + end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs index 4cc199550..793293abd 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs @@ -671,13 +671,21 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do test "returns object with emoji when object contains list tags" do assert Transmogrifier.fix_emoji(%{ "tag" => [ - %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}, + %{ + "type" => "Emoji", + "name" => ":bib:", + "icon" => %{"url" => "https://example.com/test"} + }, %{"type" => "Hashtag"} ] }) == %{ - "emoji" => %{"bib" => "/test"}, + "emoji" => %{"bib" => "https://example.com/test"}, "tag" => [ - %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}, + %{ + "icon" => %{"url" => "https://example.com/test"}, + "name" => ":bib:", + "type" => "Emoji" + }, %{"type" => "Hashtag"} ] } @@ -685,10 +693,28 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do test "returns object with emoji when object contains map tag" do assert Transmogrifier.fix_emoji(%{ - "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}} + "tag" => %{ + "type" => "Emoji", + "name" => ":bib:", + "icon" => %{"url" => "https://example.com/test"} + } }) == %{ - "emoji" => %{"bib" => "/test"}, - "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"} + "emoji" => %{"bib" => "https://example.com/test"}, + "tag" => %{ + "icon" => %{"url" => "https://example.com/test"}, + "name" => ":bib:", + "type" => "Emoji" + } + } + end + end + + describe "fix_tag/1" do + test "ignores malformed Hashtag names and tag entries" do + tag = %{"type" => "Hashtag", "name" => 123} + + assert Transmogrifier.fix_tag(%{"tag" => [tag, "malformed"]}) == %{ + "tag" => [tag, "malformed"] } end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs index 851c60850..cf995e143 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs @@ -50,6 +50,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.UserUpdateHandlingTest do assert user.bio == "

Some bio

" end + test "it selects an actor type after an extension type" do + user = insert(:user, local: false) + update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!() + + object = + update_data["object"] + |> Map.put("actor", user.ap_id) + |> Map.put("id", user.ap_id) + |> Map.put("type", ["https://example.com/ns#CustomActor", "Person"]) + + update_data = + update_data + |> Map.put("actor", user.ap_id) + |> Map.put("object", object) + + assert {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(update_data) + assert User.get_cached_by_ap_id(user.ap_id).name == "gargle" + end + test "it works with alsoKnownAs" do %{ap_id: actor} = insert(:user, local: false)