use Ecto.Schema
alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.User
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
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"])
def cast_and_validate(data) do
data
+ |> maybe_fetch_object()
|> cast_data
|> validate_data
end
|> 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
alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
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)
}
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()
}