diff --git a/changelog.d/fix-webfinger-spoofing.security b/changelog.d/fix-webfinger-spoofing.security new file mode 100644 index 0000000000..7b3c9490a8 --- /dev/null +++ b/changelog.d/fix-webfinger-spoofing.security @@ -0,0 +1 @@ +Fix webfinger spoofing. diff --git a/changelog.d/status-notification-type.add b/changelog.d/status-notification-type.add new file mode 100644 index 0000000000..a6e94fa870 --- /dev/null +++ b/changelog.d/status-notification-type.add @@ -0,0 +1 @@ +Add "status" notification type \ No newline at end of file diff --git a/changelog.d/webfinger-validation.fix b/changelog.d/webfinger-validation.fix new file mode 100644 index 0000000000..e643126665 --- /dev/null +++ b/changelog.d/webfinger-validation.fix @@ -0,0 +1 @@ +Fix validate_webfinger when running a different domain for Webfinger \ No newline at end of file diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 2cbc2f0c5b..2a54403283 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -165,10 +165,10 @@ defp cachex_children do expiration: chat_message_id_idempotency_key_expiration(), limit: 500_000 ), - build_cachex("anti_duplication_mrf", limit: 5_000), - build_cachex("translations", default_ttl: :timer.hours(24), limit: 5_000), build_cachex("rel_me", default_ttl: :timer.minutes(30), limit: 2_500), build_cachex("host_meta", default_ttl: :timer.minutes(120), limit: 5000), + build_cachex("anti_duplication_mrf", limit: 5_000), + build_cachex("translations", default_ttl: :timer.hours(24), limit: 5_000), build_cachex("domain", limit: 2500) ] end diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index f6b8ebffdf..8e0e819fd9 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -399,6 +399,8 @@ defp do_create_notifications(%Activity{} = activity) do get_notified_subscribers_from_activity(activity) -- (enabled_participants ++ enabled_receivers) + enabled_subscribers = get_notified_subscribers_from_activity(activity) + notifications = (Enum.map(enabled_receivers, fn user -> create_notification(activity, user) @@ -607,7 +609,7 @@ def get_notified_participants_from_activity( Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end) end - def get_notified_participants_from_activity(_, _), do: {[], []} + def get_notified_participants_from_activity(_, _), do: [] # For some activities, only notify the author of the object def get_potential_receiver_ap_ids(%{data: %{"type" => type, "object" => object_id}}) diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index c067c62152..f53114cd99 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -108,6 +108,9 @@ def render( type when type in ["mention", "status", "poll", "pleroma:event_reminder"] -> put_status(response, activity, reading_user, status_render_opts) + "status" -> + put_status(response, activity, reading_user, status_render_opts) + "favourite" -> put_status(response, parent_activity_fn.(), reading_user, status_render_opts) diff --git a/priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs b/priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs index 62c0afb63a..cea32f6c33 100644 --- a/priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs +++ b/priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs @@ -36,7 +36,7 @@ def down do 'reblog', 'favourite', 'pleroma:report', - 'poll + 'poll', ) """ |> execute() diff --git a/priv/repo/migrations/20220819171321_add_pleroma_participation_accepted_to_notifications_enum.exs b/priv/repo/migrations/20220819171321_add_pleroma_participation_accepted_to_notifications_enum.exs index 0ad342040d..60b55d2ef4 100644 --- a/priv/repo/migrations/20220819171321_add_pleroma_participation_accepted_to_notifications_enum.exs +++ b/priv/repo/migrations/20220819171321_add_pleroma_participation_accepted_to_notifications_enum.exs @@ -52,7 +52,8 @@ def down do 'favourite', 'pleroma:report', 'poll', - 'status' + 'status', + 'update' ) """ |> execute() diff --git a/test/fixtures/webfinger/graf-imposter-webfinger.json b/test/fixtures/webfinger/graf-imposter-webfinger.json new file mode 100644 index 0000000000..e7010f606d --- /dev/null +++ b/test/fixtures/webfinger/graf-imposter-webfinger.json @@ -0,0 +1,41 @@ +{ + "subject": "acct:graf@poa.st", + "aliases": [ + "https://fba.ryona.agenc/webfingertest" + ], + "links": [ + { + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html", + "href": "https://fba.ryona.agenc/webfingertest" + }, + { + "rel": "self", + "type": "application/activity+json", + "href": "https://fba.ryona.agenc/webfingertest" + }, + { + "rel": "http://ostatus.org/schema/1.0/subscribe", + "template": "https://fba.ryona.agenc/contact/follow?url={uri}" + }, + { + "rel": "http://schemas.google.com/g/2010#updates-from", + "type": "application/atom+xml", + "href": "" + }, + { + "rel": "salmon", + "href": "https://fba.ryona.agenc/salmon/friendica" + }, + { + "rel": "http://microformats.org/profile/hcard", + "type": "text/html", + "href": "https://fba.ryona.agenc/hcard/friendica" + }, + { + "rel": "http://joindiaspora.com/seed_location", + "type": "text/html", + "href": "https://fba.ryona.agenc" + } + ] +} diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs index 9fd1964289..4f256d26f5 100644 --- a/test/pleroma/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -204,6 +204,21 @@ test "doesn't create notification for events without participation approval" do assert length(user_notifications) == 0 end + test "does not create subscriber notification if mentioned" do + user = insert(:user) + subscriber = insert(:user) + + User.subscribe(subscriber, user) + + {:ok, status} = CommonAPI.post(user, %{status: "mentioning @#{subscriber.nickname}"}) + {:ok, [notification] = notifications} = Notification.create_notifications(status) + + assert length(notifications) == 1 + + assert notification.user_id == subscriber.id + assert notification.type == "mention" + end + test "it sends edited notifications to those who repeated a status" do user = insert(:user) repeated_user = insert(:user) diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs index 73b2ab2364..0c14a3edff 100644 --- a/test/pleroma/web/mastodon_api/views/notification_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/notification_view_test.exs @@ -332,4 +332,31 @@ test "muted notification" do test_notifications_rendering([notification], user, [expected]) end + + test "Subscribed status notification" do + user = insert(:user) + subscriber = insert(:user) + + User.subscribe(subscriber, user) + + {:ok, activity} = CommonAPI.post(user, %{status: "hi"}) + {:ok, [notification]} = Notification.create_notifications(activity) + + user = User.get_cached_by_id(user.id) + + expected = %{ + id: to_string(notification.id), + pleroma: %{is_seen: false, is_muted: false}, + type: "status", + account: + AccountView.render("show.json", %{ + user: user, + for: subscriber + }), + status: StatusView.render("show.json", %{activity: activity, for: subscriber}), + created_at: Utils.to_masto_date(notification.inserted_at) + } + + test_notifications_rendering([notification], subscriber, [expected]) + end end diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs index 501841daaa..23aefb5640 100644 --- a/test/pleroma/web/web_finger_test.exs +++ b/test/pleroma/web/web_finger_test.exs @@ -226,4 +226,18 @@ test "prevents spoofing" do {:error, _data} = WebFinger.finger("alex@gleasonator.com") end end + + @tag capture_log: true + test "prevents forgeries" do + Tesla.Mock.mock(fn + %{url: "https://fba.ryona.agency/.well-known/webfinger?resource=acct:graf@fba.ryona.agency"} -> + fake_webfinger = + File.read!("test/fixtures/webfinger/graf-imposter-webfinger.json") |> Jason.decode!() + + Tesla.Mock.json(fake_webfinger) + + %{url: "https://fba.ryona.agency/.well-known/host-meta"} -> + {:ok, %Tesla.Env{status: 404}} + end) + end end diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 50216470ea..f88d7253f5 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -1693,6 +1693,120 @@ def get("https://friends.grishka.me/users/1", _, _, _) do }} end + def get("https://mastodon.example/.well-known/host-meta", _, _, _) do + {:ok, + %Tesla.Env{ + status: 302, + headers: [{"location", "https://sub.mastodon.example/.well-known/host-meta"}] + }} + end + + def get("https://sub.mastodon.example/.well-known/host-meta", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/masto-host-meta.xml" + |> File.read!() + |> String.replace("{{domain}}", "sub.mastodon.example") + }} + end + + def get( + "https://sub.mastodon.example/.well-known/webfinger?resource=acct:a@mastodon.example", + _, + _, + _ + ) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/masto-webfinger.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "mastodon.example") + |> String.replace("{{subdomain}}", "sub.mastodon.example"), + headers: [{"content-type", "application/jrd+json"}] + }} + end + + def get("https://sub.mastodon.example/users/a", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/masto-user.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "sub.mastodon.example"), + headers: [{"content-type", "application/activity+json"}] + }} + end + + def get("https://sub.mastodon.example/users/a/collections/featured", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: + File.read!("test/fixtures/users_mock/masto_featured.json") + |> String.replace("{{domain}}", "sub.mastodon.example") + |> String.replace("{{nickname}}", "a"), + headers: [{"content-type", "application/activity+json"}] + }} + end + + def get("https://pleroma.example/.well-known/host-meta", _, _, _) do + {:ok, + %Tesla.Env{ + status: 302, + headers: [{"location", "https://sub.pleroma.example/.well-known/host-meta"}] + }} + end + + def get("https://sub.pleroma.example/.well-known/host-meta", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/pleroma-host-meta.xml" + |> File.read!() + |> String.replace("{{domain}}", "sub.pleroma.example") + }} + end + + def get( + "https://sub.pleroma.example/.well-known/webfinger?resource=acct:a@pleroma.example", + _, + _, + _ + ) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/pleroma-webfinger.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "pleroma.example") + |> String.replace("{{subdomain}}", "sub.pleroma.example"), + headers: [{"content-type", "application/jrd+json"}] + }} + end + + def get("https://sub.pleroma.example/users/a", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: + "test/fixtures/webfinger/pleroma-user.json" + |> File.read!() + |> String.replace("{{nickname}}", "a") + |> String.replace("{{domain}}", "sub.pleroma.example"), + headers: [{"content-type", "application/activity+json"}] + }} + end + def get(url, query, body, headers) do {:error, "Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}