commit: 9d89156b84b8d17c7b228957a142da96e74e7218
parent 63bdf4dc2bd5258d0f306eeed71e49ed1e4b17a7
Author: Phantasm <phantasm@centrum.cz>
Date: Wed, 10 Dec 2025 11:49:01 +0100
AP C2S: Explicitly reject Updates to Actors that failed silently
Diffstat:
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
@@ -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
@@ -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)