logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/
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:

Mlib/pleroma/web/activity_pub/activity_pub_controller.ex15++++++++++++---
Mtest/pleroma/web/activity_pub/activity_pub_controller_test.exs105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
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)