Resolve follow activity from accept/reject without ID (#328)

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/akkoma#328
This commit is contained in:
floatingghost 2022-12-02 11:12:37 +00:00
parent db60640c5b
commit 8d6cc6cb65
5 changed files with 120 additions and 5 deletions

View file

@ -367,6 +367,14 @@ def following_requests_for_actor(%User{ap_id: ap_id}) do
|> Repo.all()
end
def follow_activity(%User{ap_id: ap_id}, %User{ap_id: followed_ap_id}) do
Queries.by_type("Follow")
|> where([a], a.actor == ^ap_id)
|> where([a], fragment("?->>'object' = ?", a.data, ^followed_ap_id))
|> where([a], fragment("?->>'state'", a.data) in ["pending", "accept"])
|> Repo.one()
end
def restrict_deactivated_users(query) do
query
|> join(

View file

@ -105,7 +105,7 @@ def persist(%{"type" => type} = object, meta) when type in @object_types do
end
end
@unpersisted_activity_types ~w[Undo Delete Remove]
@unpersisted_activity_types ~w[Undo Delete Remove Accept Reject]
@impl true
def persist(%{"type" => type} = object, [local: false] = meta)
when type in @unpersisted_activity_types do

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
use Ecto.Schema
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.User
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@ -29,7 +31,7 @@ def cast_data(data) do
defp validate_data(cng) do
cng
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_required([:type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Accept", "Reject"])
|> validate_actor_presence()
|> validate_object_presence(allowed_types: ["Follow"])
@ -38,6 +40,7 @@ defp validate_data(cng) do
def cast_and_validate(data) do
data
|> maybe_fetch_object()
|> cast_data
|> validate_data
end
@ -53,4 +56,31 @@ def validate_accept_reject_rights(cng) do
|> add_error(:actor, "can't accept or reject the given activity")
end
end
defp maybe_fetch_object(%{"object" => %{} = object} = activity) do
# If we don't have an ID, we may have to fetch the object
if Map.has_key?(object, "id") do
# Do nothing
activity
else
Map.put(activity, "object", fetch_transient_object(object))
end
end
defp maybe_fetch_object(activity), do: activity
defp fetch_transient_object(
%{"actor" => actor, "object" => target, "type" => "Follow"} = object
) do
with %User{} = actor <- User.get_cached_by_ap_id(actor),
%User{local: true} = target <- User.get_cached_by_ap_id(target),
%Activity{} = activity <- Activity.follow_activity(actor, target) do
activity.data
else
_e ->
object
end
end
defp fetch_transient_object(_), do: {:error, "not a supported transient object"}
end

View file

@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.RejectHandlingTest do
alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
@ -53,6 +54,81 @@ test "it works for incoming rejects which are referenced by IRI only" do
assert User.following?(follower, followed) == false
end
describe "when accept/reject references a transient activity" do
test "it handles accept activities that do not contain an ID key" do
follower = insert(:user)
followed = insert(:user, is_locked: true)
pending_follow =
insert(:follow_activity, follower: follower, followed: followed, state: "pending")
refute User.following?(follower, followed)
without_id = Map.delete(pending_follow.data, "id")
reject_data =
File.read!("test/fixtures/mastodon-reject-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.ap_id)
|> Map.delete("id")
|> Map.put("object", without_id)
{:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
follower = User.get_cached_by_id(follower.id)
refute User.following?(follower, followed)
assert Utils.fetch_latest_follow(follower, followed).data["state"] == "reject"
end
test "it handles reject activities that do not contain an ID key" do
follower = insert(:user)
followed = insert(:user)
{:ok, follower, followed} = User.follow(follower, followed)
{:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
assert Utils.fetch_latest_follow(follower, followed).data["state"] == "accept"
assert User.following?(follower, followed)
without_id = Map.delete(follow_activity.data, "id")
reject_data =
File.read!("test/fixtures/mastodon-reject-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.ap_id)
|> Map.delete("id")
|> Map.put("object", without_id)
{:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
follower = User.get_cached_by_id(follower.id)
refute User.following?(follower, followed)
assert Utils.fetch_latest_follow(follower, followed).data["state"] == "reject"
end
test "it does not accept follows that are not in pending or accepted" do
follower = insert(:user)
followed = insert(:user, is_locked: true)
rejected_follow =
insert(:follow_activity, follower: follower, followed: followed, state: "reject")
refute User.following?(follower, followed)
without_id = Map.delete(rejected_follow.data, "id")
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
|> Jason.decode!()
|> Map.put("actor", followed.ap_id)
|> Map.put("object", without_id)
{:error, _} = Transmogrifier.handle_incoming(accept_data)
refute User.following?(follower, followed)
end
end
test "it rejects activities without a valid ID" do
user = insert(:user)

View file

@ -452,15 +452,16 @@ def like_activity_factory(attrs \\ %{}) do
}
end
def follow_activity_factory do
follower = insert(:user)
followed = insert(:user)
def follow_activity_factory(attrs \\ %{}) do
follower = attrs[:follower] || insert(:user)
followed = attrs[:followed] || insert(:user)
data = %{
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
"actor" => follower.ap_id,
"type" => "Follow",
"object" => followed.ap_id,
"state" => attrs[:state] || "pending",
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601()
}