Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85829834
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
8 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/changelog.d/c2s-hardening.security b/changelog.d/c2s-hardening.security
index a77275e41..a71038a1a 100644
--- a/changelog.d/c2s-hardening.security
+++ b/changelog.d/c2s-hardening.security
@@ -3,3 +3,4 @@ Enforce poll voting rules (visible, open poll, not the author, one vote per opti
Only allow pinning own posts, and only public or unlisted ones for local users; the featured collection skips non-public posts
Validate the object of local Updates; the server builds the edit history and edit time and keeps poll results
Hold new local polls to the poll limits and reject preset vote counts
+Reject local replies to and quotes of posts the author cannot see or quote
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index a522231ec..6ac36c3ed 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -37,6 +37,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.FlagValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.LocalObjectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.MoveValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
@@ -303,17 +304,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
defp validate_new_object(object, meta) do
if Keyword.get(meta, :local, false) do
- validate_new_local_object(object)
+ LocalObjectValidator.validate(object)
else
:ok
end
end
- defp validate_new_local_object(%{"type" => "Question"} = question),
- do: QuestionValidator.validate_local_poll(question)
-
- defp validate_new_local_object(_object), do: :ok
-
# The server owns the edit history, the edit time and the poll results of
# local objects. Only the editable fields come from the new version.
defp maybe_rebuild_local_update_object(%{"id" => id} = object, meta) when is_binary(id) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/local_object_validator.ex b/lib/pleroma/web/activity_pub/object_validators/local_object_validator.ex
new file mode 100644
index 000000000..00843dbe4
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/local_object_validator.ex
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.LocalObjectValidator do
+ @moduledoc """
+ Rules for new objects of local users. CommonAPI builds objects that follow
+ them; C2S clients send their own objects, so the pipeline checks them.
+ Remote objects follow the rules of their own server.
+ """
+
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
+ alias Pleroma.Web.ActivityPub.Visibility
+
+ @spec validate(map()) :: :ok | {:error, String.t()}
+ def validate(%{"actor" => actor} = object) do
+ user = User.get_cached_by_ap_id(actor)
+
+ with :ok <- validate_in_reply_to(object, user),
+ :ok <- validate_quote(object, user) do
+ validate_type(object)
+ end
+ end
+
+ defp validate_type(%{"type" => "Question"} = question),
+ do: QuestionValidator.validate_local_poll(question)
+
+ defp validate_type(_object), do: :ok
+
+ # Unknown objects can't leak anything, so only known ones are checked.
+ defp validate_in_reply_to(%{"inReplyTo" => id}, user) when is_binary(id) do
+ case Object.normalize(id, fetch: false) do
+ %Object{} = object ->
+ if Visibility.visible_for_user?(object, user),
+ do: :ok,
+ else: {:error, "Can't reply to an invisible object"}
+
+ nil ->
+ :ok
+ end
+ end
+
+ defp validate_in_reply_to(_object, _user), do: :ok
+
+ defp validate_quote(%{"quoteUrl" => id}, %User{} = user) when is_binary(id) do
+ case Object.normalize(id, fetch: false) do
+ %Object{} = object ->
+ if Visibility.visible_for_user?(object, user) and Visibility.quotable?(object, user.ap_id),
+ do: :ok,
+ else: {:error, "Can't quote this object"}
+
+ nil ->
+ :ok
+ end
+ end
+
+ defp validate_quote(_object, _user), do: :ok
+end
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 903804d0d..7d4b43572 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -136,6 +136,16 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key)
end
+ @doc "Whether the actor may quote the object: public posts, or own followers-only posts."
+ @spec quotable?(Object.t(), String.t()) :: boolean()
+ def quotable?(%Object{} = object, actor_ap_id) do
+ case get_visibility(object) do
+ visibility when visibility in ~w(public unlisted local) -> true
+ "private" -> object.data["actor"] == actor_ap_id
+ _ -> false
+ end
+ end
+
def get_visibility(object) do
to = object.data["to"] || []
cc = object.data["cc"] || []
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index 2427d1298..0cc8ec524 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -211,21 +211,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
end
end
- defp can_quote?(_draft, _object, visibility) when visibility in ~w(public unlisted local) do
- true
- end
-
- defp can_quote?(draft, object, "private") do
- draft.user.ap_id == object.data["actor"]
- end
-
- defp can_quote?(_, _, _) do
- false
- end
-
+ # Checked here too, because previews skip the pipeline.
defp quoting_visibility(%__MODULE__{quote_post: %Activity{}} = draft) do
with %Object{} = object <- Object.normalize(draft.quote_post, fetch: false),
- true <- can_quote?(draft, object, Visibility.get_visibility(object)) do
+ true <- Visibility.quotable?(object, draft.user.ap_id) do
draft
else
_ -> add_error(draft, dgettext("errors", "Cannot quote private message"))
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_c2s_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_c2s_test.exs
index b5dc0f220..b58af24c0 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_c2s_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_c2s_test.exs
@@ -323,4 +323,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerC2STest do
end
end
end
+
+ describe "replies and quotes" do
+ setup do
+ victim = insert(:user)
+ {:ok, secret} = CommonAPI.post(victim, %{status: "secret", visibility: "private"})
+ %{victim: victim, secret_id: secret.data["object"]}
+ end
+
+ defp note(extra) do
+ object =
+ Map.merge(%{"type" => "Note", "content" => "hi", "to" => [@public], "cc" => []}, extra)
+
+ %{"type" => "Create", "object" => object}
+ end
+
+ test "rejects a reply to a post the user cannot see", %{conn: conn, secret_id: id} do
+ post_outbox(conn, insert(:user), note(%{"inReplyTo" => id}), 400)
+ assert Object.get_by_ap_id(id).data["repliesCount"] in [nil, 0]
+ end
+
+ test "rejects a quote of a private post of another user", %{conn: conn} = ctx do
+ {:ok, follower, _} = Pleroma.User.follow(insert(:user), ctx.victim)
+ post_outbox(conn, follower, note(%{"quoteUrl" => ctx.secret_id}), 400)
+ assert Object.get_by_ap_id(ctx.secret_id).data["quotesCount"] in [nil, 0]
+ end
+
+ test "allows a quote of an own private post", %{conn: conn} = ctx do
+ result = post_outbox(conn, ctx.victim, note(%{"quoteUrl" => ctx.secret_id}), 201)
+ assert Object.normalize(result["object"], fetch: false).data["quoteUrl"] == ctx.secret_id
+ end
+ end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Nov 1, 12:50 AM (14 h, 22 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1801999
Default Alt Text
(8 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline