Validate WebFinger nicknames against actors
This commit is contained in:
parent
6ae02d71bd
commit
621d86a31d
2 changed files with 161 additions and 40 deletions
|
|
@ -1677,44 +1677,80 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
show_birthday = !!birthday
|
show_birthday = !!birthday
|
||||||
|
|
||||||
# if WebFinger request was already done, we probably have acct, otherwise
|
with {:ok, nickname} <- nickname_from_actor(data, additional) do
|
||||||
# we request WebFinger here
|
{:ok,
|
||||||
nickname = additional[:nickname_from_acct] || generate_nickname(data)
|
%{
|
||||||
|
ap_id: data["id"],
|
||||||
|
uri: get_actor_url(data["url"]),
|
||||||
|
banner: normalize_image(data["image"]),
|
||||||
|
fields: fields,
|
||||||
|
emoji: emojis,
|
||||||
|
is_locked: is_locked,
|
||||||
|
is_discoverable: is_discoverable,
|
||||||
|
invisible: invisible,
|
||||||
|
avatar: normalize_image(data["icon"]),
|
||||||
|
name: data["name"],
|
||||||
|
follower_address: data["followers"],
|
||||||
|
following_address: data["following"],
|
||||||
|
featured_address: featured_address,
|
||||||
|
bio: data["summary"] || "",
|
||||||
|
actor_type: actor_type,
|
||||||
|
also_known_as: normalize_also_known_as(data["alsoKnownAs"]),
|
||||||
|
public_key: public_key,
|
||||||
|
inbox: data["inbox"],
|
||||||
|
shared_inbox: shared_inbox,
|
||||||
|
accepts_chat_messages: accepts_chat_messages,
|
||||||
|
birthday: birthday,
|
||||||
|
show_birthday: show_birthday,
|
||||||
|
pinned_objects: pinned_objects,
|
||||||
|
nickname: nickname
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
%{
|
defp nickname_from_actor(data, additional) do
|
||||||
ap_id: data["id"],
|
generated = generated_nickname(data)
|
||||||
uri: get_actor_url(data["url"]),
|
|
||||||
banner: normalize_image(data["image"]),
|
case additional[:nickname_from_acct] do
|
||||||
fields: fields,
|
^generated when is_binary(generated) ->
|
||||||
emoji: emojis,
|
{:ok, generated}
|
||||||
is_locked: is_locked,
|
|
||||||
is_discoverable: is_discoverable,
|
acct when is_binary(acct) ->
|
||||||
invisible: invisible,
|
with ^acct <- webfinger_nickname(data) do
|
||||||
avatar: normalize_image(data["icon"]),
|
{:ok, acct}
|
||||||
name: data["name"],
|
else
|
||||||
follower_address: data["followers"],
|
_ -> {:error, {:webfinger_actor_mismatch, acct, data["id"]}}
|
||||||
following_address: data["following"],
|
end
|
||||||
featured_address: featured_address,
|
|
||||||
bio: data["summary"] || "",
|
_ ->
|
||||||
actor_type: actor_type,
|
{:ok, generate_nickname(data)}
|
||||||
also_known_as: normalize_also_known_as(data["alsoKnownAs"]),
|
end
|
||||||
public_key: public_key,
|
end
|
||||||
inbox: data["inbox"],
|
|
||||||
shared_inbox: shared_inbox,
|
defp generated_nickname(%{"preferredUsername" => username, "id" => ap_id})
|
||||||
accepts_chat_messages: accepts_chat_messages,
|
when is_binary(username) and is_binary(ap_id) do
|
||||||
birthday: birthday,
|
case URI.parse(ap_id) do
|
||||||
show_birthday: show_birthday,
|
%URI{host: host} when is_binary(host) -> "#{username}@#{host}"
|
||||||
pinned_objects: pinned_objects,
|
_ -> nil
|
||||||
nickname: nickname
|
end
|
||||||
}
|
end
|
||||||
|
|
||||||
|
defp generated_nickname(_), do: nil
|
||||||
|
|
||||||
|
defp webfinger_nickname(data) do
|
||||||
|
with generated when is_binary(generated) <- generated_nickname(data),
|
||||||
|
{:ok, %{"subject" => "acct:" <> acct, "ap_id" => ap_id}} <- WebFinger.finger(generated),
|
||||||
|
true <- ap_id == data["id"] do
|
||||||
|
acct
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp generate_nickname(%{"preferredUsername" => username} = data) when is_binary(username) do
|
defp generate_nickname(%{"preferredUsername" => username} = data) when is_binary(username) do
|
||||||
generated = "#{username}@#{URI.parse(data["id"]).host}"
|
generated = generated_nickname(data)
|
||||||
|
|
||||||
if Config.get([WebFinger, :update_nickname_on_user_fetch]) do
|
if Config.get([WebFinger, :update_nickname_on_user_fetch]) do
|
||||||
case WebFinger.finger(generated) do
|
case webfinger_nickname(data) do
|
||||||
{:ok, %{"subject" => "acct:" <> acct}} -> acct
|
acct when is_binary(acct) -> acct
|
||||||
_ -> generated
|
_ -> generated
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
@ -1794,9 +1830,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
defp collection_private(_data), do: {:ok, true}
|
defp collection_private(_data), do: {:ok, true}
|
||||||
|
|
||||||
def user_data_from_user_object(data, additional \\ []) do
|
def user_data_from_user_object(data, additional \\ []) do
|
||||||
with {:ok, data} <- MRF.filter(data) do
|
with {:ok, data} <- MRF.filter(data),
|
||||||
{:ok, object_to_user_data(data, additional)}
|
{:ok, data} <- object_to_user_data(data, additional) do
|
||||||
|
{:ok, data}
|
||||||
else
|
else
|
||||||
|
{:error, _} = e -> e
|
||||||
e -> {:error, e}
|
e -> {:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -876,17 +876,17 @@ defmodule Pleroma.UserTest do
|
||||||
describe "get_or_fetch/1 remote users with tld, while BE is running on a subdomain" do
|
describe "get_or_fetch/1 remote users with tld, while BE is running on a subdomain" do
|
||||||
setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true)
|
setup do: clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true)
|
||||||
|
|
||||||
test "for mastodon" do
|
test "fetches a mastodon split-domain nickname" do
|
||||||
ap_id = "a@mastodon.example"
|
nickname = "a@mastodon.example"
|
||||||
{:ok, fetched_user} = User.get_or_fetch(ap_id)
|
{:ok, fetched_user} = User.get_or_fetch(nickname)
|
||||||
|
|
||||||
assert fetched_user.ap_id == "https://sub.mastodon.example/users/a"
|
assert fetched_user.ap_id == "https://sub.mastodon.example/users/a"
|
||||||
assert fetched_user.nickname == "a@mastodon.example"
|
assert fetched_user.nickname == "a@mastodon.example"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "for pleroma" do
|
test "fetches a pleroma split-domain nickname" do
|
||||||
ap_id = "a@pleroma.example"
|
nickname = "a@pleroma.example"
|
||||||
{:ok, fetched_user} = User.get_or_fetch(ap_id)
|
{:ok, fetched_user} = User.get_or_fetch(nickname)
|
||||||
|
|
||||||
assert fetched_user.ap_id == "https://sub.pleroma.example/users/a"
|
assert fetched_user.ap_id == "https://sub.pleroma.example/users/a"
|
||||||
assert fetched_user.nickname == "a@pleroma.example"
|
assert fetched_user.nickname == "a@pleroma.example"
|
||||||
|
|
@ -936,6 +936,89 @@ defmodule Pleroma.UserTest do
|
||||||
assert fetched_user == "not found nonexistent"
|
assert fetched_user == "not found nonexistent"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "does not rename an existing remote actor from rogue WebFinger data" do
|
||||||
|
clear_config([Pleroma.Web.WebFinger, :update_nickname_on_user_fetch], true)
|
||||||
|
|
||||||
|
actor_id = "https://legit-actor.example/users/alice"
|
||||||
|
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{url: "https://evil-webfinger.example/.well-known/host-meta"} ->
|
||||||
|
{:ok, %Tesla.Env{status: 404}}
|
||||||
|
|
||||||
|
%{
|
||||||
|
url:
|
||||||
|
"https://evil-webfinger.example/.well-known/webfinger?resource=acct:claimed@evil-webfinger.example"
|
||||||
|
} ->
|
||||||
|
Tesla.Mock.json(%{
|
||||||
|
"subject" => "acct:claimed@evil-webfinger.example",
|
||||||
|
"links" => [
|
||||||
|
%{
|
||||||
|
"rel" => "self",
|
||||||
|
"type" => "application/activity+json",
|
||||||
|
"href" => actor_id
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
%{url: ^actor_id} ->
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
headers: [{"content-type", "application/activity+json"}],
|
||||||
|
body:
|
||||||
|
Jason.encode!(%{
|
||||||
|
"id" => actor_id,
|
||||||
|
"type" => "Person",
|
||||||
|
"preferredUsername" => "alice",
|
||||||
|
"name" => "Alice",
|
||||||
|
"summary" => "",
|
||||||
|
"inbox" => "https://legit-actor.example/users/alice/inbox",
|
||||||
|
"outbox" => "https://legit-actor.example/users/alice/outbox",
|
||||||
|
"followers" => "https://legit-actor.example/users/alice/followers",
|
||||||
|
"following" => "https://legit-actor.example/users/alice/following"
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
|
||||||
|
%{url: "https://legit-actor.example/.well-known/host-meta"} ->
|
||||||
|
{:ok, %Tesla.Env{status: 404}}
|
||||||
|
|
||||||
|
%{
|
||||||
|
url:
|
||||||
|
"https://legit-actor.example/.well-known/webfinger?resource=acct:alice@legit-actor.example"
|
||||||
|
} ->
|
||||||
|
Tesla.Mock.json(%{
|
||||||
|
"subject" => "acct:alice@legit-actor.example",
|
||||||
|
"links" => [
|
||||||
|
%{
|
||||||
|
"rel" => "self",
|
||||||
|
"type" => "application/activity+json",
|
||||||
|
"href" => actor_id
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:error, {:webfinger_actor_mismatch, "claimed@evil-webfinger.example", ^actor_id}} =
|
||||||
|
ActivityPub.make_user_from_nickname("claimed@evil-webfinger.example")
|
||||||
|
|
||||||
|
refute User.get_by_ap_id(actor_id)
|
||||||
|
refute User.get_by_nickname("claimed@evil-webfinger.example")
|
||||||
|
|
||||||
|
orig_user =
|
||||||
|
insert(:user,
|
||||||
|
local: false,
|
||||||
|
nickname: "alice@legit-actor.example",
|
||||||
|
ap_id: actor_id
|
||||||
|
)
|
||||||
|
|
||||||
|
assert {:error, {:webfinger_actor_mismatch, "claimed@evil-webfinger.example", ^actor_id}} =
|
||||||
|
ActivityPub.make_user_from_nickname("claimed@evil-webfinger.example")
|
||||||
|
|
||||||
|
assert {:error, _} = User.get_or_fetch_by_nickname("claimed@evil-webfinger.example")
|
||||||
|
assert User.get_by_id(orig_user.id).nickname == "alice@legit-actor.example"
|
||||||
|
refute User.get_by_nickname("claimed@evil-webfinger.example")
|
||||||
|
end
|
||||||
|
|
||||||
test "updates an existing user, if stale" do
|
test "updates an existing user, if stale" do
|
||||||
a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800)
|
a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue