Resolve follow activity from accept/reject without ID (#328)
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk> Reviewed-on: #328
This commit is contained in:
parent
db60640c5b
commit
8d6cc6cb65
5 changed files with 120 additions and 5 deletions
|
@ -367,6 +367,14 @@ def following_requests_for_actor(%User{ap_id: ap_id}) do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
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
|
def restrict_deactivated_users(query) do
|
||||||
query
|
query
|
||||||
|> join(
|
|> join(
|
||||||
|
|
|
@ -105,7 +105,7 @@ def persist(%{"type" => type} = object, meta) when type in @object_types do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@unpersisted_activity_types ~w[Undo Delete Remove]
|
@unpersisted_activity_types ~w[Undo Delete Remove Accept Reject]
|
||||||
@impl true
|
@impl true
|
||||||
def persist(%{"type" => type} = object, [local: false] = meta)
|
def persist(%{"type" => type} = object, [local: false] = meta)
|
||||||
when type in @unpersisted_activity_types do
|
when type in @unpersisted_activity_types do
|
||||||
|
|
|
@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
@ -29,7 +31,7 @@ def cast_data(data) do
|
||||||
|
|
||||||
defp validate_data(cng) do
|
defp validate_data(cng) do
|
||||||
cng
|
cng
|
||||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
|> validate_required([:type, :actor, :to, :cc, :object])
|
||||||
|> validate_inclusion(:type, ["Accept", "Reject"])
|
|> validate_inclusion(:type, ["Accept", "Reject"])
|
||||||
|> validate_actor_presence()
|
|> validate_actor_presence()
|
||||||
|> validate_object_presence(allowed_types: ["Follow"])
|
|> validate_object_presence(allowed_types: ["Follow"])
|
||||||
|
@ -38,6 +40,7 @@ defp validate_data(cng) do
|
||||||
|
|
||||||
def cast_and_validate(data) do
|
def cast_and_validate(data) do
|
||||||
data
|
data
|
||||||
|
|> maybe_fetch_object()
|
||||||
|> cast_data
|
|> cast_data
|
||||||
|> validate_data
|
|> validate_data
|
||||||
end
|
end
|
||||||
|
@ -53,4 +56,31 @@ def validate_accept_reject_rights(cng) do
|
||||||
|> add_error(:actor, "can't accept or reject the given activity")
|
|> add_error(:actor, "can't accept or reject the given activity")
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.RejectHandlingTest do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
import Pleroma.Factory
|
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
|
assert User.following?(follower, followed) == false
|
||||||
end
|
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
|
test "it rejects activities without a valid ID" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
|
|
@ -452,15 +452,16 @@ def like_activity_factory(attrs \\ %{}) do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_activity_factory do
|
def follow_activity_factory(attrs \\ %{}) do
|
||||||
follower = insert(:user)
|
follower = attrs[:follower] || insert(:user)
|
||||||
followed = insert(:user)
|
followed = attrs[:followed] || insert(:user)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||||
"actor" => follower.ap_id,
|
"actor" => follower.ap_id,
|
||||||
"type" => "Follow",
|
"type" => "Follow",
|
||||||
"object" => followed.ap_id,
|
"object" => followed.ap_id,
|
||||||
|
"state" => attrs[:state] || "pending",
|
||||||
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601()
|
"published_at" => DateTime.utc_now() |> DateTime.to_iso8601()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue