From 9d89156b84b8d17c7b228957a142da96e74e7218 Mon Sep 17 00:00:00 2001 From: Phantasm Date: Wed, 10 Dec 2025 11:49:01 +0100 Subject: [PATCH] AP C2S: Explicitly reject Updates to Actors that failed silently --- .../activity_pub/activity_pub_controller.ex | 15 ++- .../activity_pub_controller_test.exs | 105 +++++++++++++++++- 2 files changed, 115 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 5a6ffa156..df5fcf9f6 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -486,10 +486,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do # both send it straight to ActivityPub.flag and C2S currently has to go through # the normal pipeline which requires an ObjectValidator. # TODO: Add a Flag Activity ObjectValidator - defp validate_visibility(_, %{"type" => "Flag"}) do + defp check_allowed_action(_, %{"type" => "Flag"}) do {:error, "Flag activities aren't currently supported in C2S"} end + # It would respond with 201 and silently fail with: + # Could not decode featured collection at fetch #{user.ap_id} \ + # {:error, "Trying to fetch local resource"} + defp check_allowed_action(%{ap_id: ap_id}, %{"type" => "Update", "object" => %{"id" => ap_id}}), + do: {:error, "Updating profile is not currently supported in C2S"} + + defp check_allowed_action(_, activity), do: {:ok, activity} + defp validate_visibility(%User{} = user, %{"type" => type, "object" => object} = activity) do with {_, %Object{} = normalized_object} <- {:normalize, Object.normalize(object, fetch: false)}, @@ -521,8 +529,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do |> Map.put("actor", actor) with {:ok, params} <- fix_user_message(user, params), - {:ok, activity} <- validate_visibility(user, params), - {:ok, activity, _} <- Pipeline.common_pipeline(activity, local: true), + {:ok, params} <- check_allowed_action(user, params), + {:ok, params} <- validate_visibility(user, params), + {:ok, activity, _} <- Pipeline.common_pipeline(params, local: true), %Activity{data: activity_data} <- Activity.normalize(activity) do conn |> put_status(:created) diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 812a45e7c..8599cf516 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -1716,7 +1716,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do data = %{ type: "Add", - target: "#{Pleroma.Web.Endpoint.url()}/users/#{target_user.nickname}/collections/featured", + target: + "#{Pleroma.Web.Endpoint.url()}/users/#{target_user.nickname}/collections/featured", object: object_id } @@ -1739,7 +1740,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do data = %{ type: "Remove", - target: "#{Pleroma.Web.Endpoint.url()}/users/#{target_user.nickname}/collections/featured", + target: + "#{Pleroma.Web.Endpoint.url()}/users/#{target_user.nickname}/collections/featured", object: object_id } @@ -1752,6 +1754,105 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert json_response(conn, 400) end + test "it rejects updating Actor's profile", %{conn: conn} do + user = insert(:user, local: true) + + user_object = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user}) + user_object_new = Map.put(user_object, "name", "lain") + + data = %{ + type: "Update", + object: user_object_new + } + + conn = + conn + |> assign(:user, user) + |> put_req_header("content-type", "application/json") + |> post("/users/#{user.nickname}/outbox", data) + + updated_user_object = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user}) + + assert updated_user_object == user_object + assert json_response(conn, 400) + end + + # Actor publicKey tests are redundant with above test, + # left here for the case that Updating Actors is ever supported + test "it rejects updating Actor's publicKey", %{conn: conn} do + user = insert(:user, local: true) + + {:ok, pem} = Pleroma.Keys.generate_rsa_pem() + {:ok, _, public_key} = Pleroma.Keys.keys_from_pem(pem) + # Taken from UserView + public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) + public_key = :public_key.pem_encode([public_key]) + + user_object = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user}) + user_object_public_key = Map.fetch!(user_object, "publicKey") + user_object_public_key = Map.put(user_object_public_key, "publicKeyPem", public_key) + user_object_new = Map.put(user_object, "publicKey", user_object_public_key) + + refute user_object == user_object_new + + data = %{ + type: "Update", + object: user_object_new + } + + conn = + conn + |> assign(:user, user) + |> put_req_header("content-type", "application/json") + |> post("/users/#{user.nickname}/outbox", data) + + new_user_object = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user}) + + assert user_object == new_user_object + assert json_response(conn, 400) + end + + test "it rejects updating Actor's publicKey of another user", %{conn: conn} do + user = insert(:user) + target_user = insert(:user, local: true) + + {:ok, pem} = Pleroma.Keys.generate_rsa_pem() + {:ok, _, public_key} = Pleroma.Keys.keys_from_pem(pem) + # Taken from UserView + public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) + public_key = :public_key.pem_encode([public_key]) + + target_user_object = + Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: target_user}) + + target_user_object_public_key = Map.fetch!(target_user_object, "publicKey") + + target_user_object_public_key = + Map.put(target_user_object_public_key, "publicKeyPem", public_key) + + target_user_object_new = + Map.put(target_user_object, "publicKey", target_user_object_public_key) + + refute target_user_object == target_user_object_new + + data = %{ + type: "Update", + object: target_user_object_new + } + + conn = + conn + |> assign(:user, user) + |> put_req_header("content-type", "application/json") + |> post("/users/#{target_user.nickname}/outbox", data) + + new_target_user_object = + Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: target_user}) + + assert target_user_object == new_target_user_object + assert json_response(conn, 403) + end + test "it rejects creating Actors of type Application", %{conn: conn} do user = insert(:user, local: true)