Merge branch 'cancel-follow-request' into 'develop'
authorHaelwenn <contact+git.pleroma.social@hacktivis.me>
Fri, 7 Feb 2020 16:10:43 +0000 (16:10 +0000)
committerHaelwenn <contact+git.pleroma.social@hacktivis.me>
Fri, 7 Feb 2020 16:10:43 +0000 (16:10 +0000)
Add support for cancellation of a follow request

Closes #1522

See merge request pleroma/pleroma!2175

CHANGELOG.md
lib/pleroma/following_relationship.ex
lib/pleroma/user.ex
lib/pleroma/web/activity_pub/utils.ex
test/web/activity_pub/activity_pub_test.exs
test/web/common_api/common_api_test.exs
test/web/mastodon_api/controllers/account_controller_test.exs

index b470b74ed12b11f37d9c5d3c73203a896d2eeace..8e316f6f65476937584fc7fc4c41dd8cbd6c8604 100644 (file)
@@ -121,6 +121,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - OTP releases: Not being able to configure OAuth expired token cleanup interval
 - OTP releases: Not being able to configure HTML sanitization policy
 - Favorites timeline now ordered by favorite date instead of post date
+- Support for cancellation of a follow request
 <details>
   <summary>API Changes</summary>
 
index 0b0219b82c3b240aa77a486e748eb943a5ba7df4..b8cb3bf0372a3960fcbbc484e77956553fcd86ca 100644 (file)
@@ -58,8 +58,8 @@ defmodule Pleroma.FollowingRelationship do
 
   def unfollow(%User{} = follower, %User{} = following) do
     case get(follower, following) do
-      nil -> {:ok, nil}
       %__MODULE__{} = following_relationship -> Repo.delete(following_relationship)
+      _ -> {:ok, nil}
     end
   end
 
index 3c86cdb38bbf1bcdf13f684a90cd89db252ec1ab..5ea36fea32ed1a770307db6814a8681c38df8d23 100644 (file)
@@ -647,25 +647,48 @@ defmodule Pleroma.User do
     end
   end
 
+  def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
+    {:error, "Not subscribed!"}
+  end
+
   def unfollow(%User{} = follower, %User{} = followed) do
-    if following?(follower, followed) and follower.ap_id != followed.ap_id do
-      FollowingRelationship.unfollow(follower, followed)
+    case get_follow_state(follower, followed) do
+      state when state in ["accept", "pending"] ->
+        FollowingRelationship.unfollow(follower, followed)
+        {:ok, followed} = update_follower_count(followed)
 
-      {:ok, followed} = update_follower_count(followed)
+        {:ok, follower} =
+          follower
+          |> update_following_count()
+          |> set_cache()
 
-      {:ok, follower} =
-        follower
-        |> update_following_count()
-        |> set_cache()
+        {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
 
-      {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
-    else
-      {:error, "Not subscribed!"}
+      nil ->
+        {:error, "Not subscribed!"}
     end
   end
 
   defdelegate following?(follower, followed), to: FollowingRelationship
 
+  def get_follow_state(%User{} = follower, %User{} = following) do
+    following_relationship = FollowingRelationship.get(follower, following)
+
+    case {following_relationship, following.local} do
+      {nil, false} ->
+        case Utils.fetch_latest_follow(follower, following) do
+          %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
+          _ -> nil
+        end
+
+      {%{state: state}, _} ->
+        state
+
+      {nil, _} ->
+        nil
+    end
+  end
+
   def locked?(%User{} = user) do
     user.locked || false
   end
index 4bcacc6d15c984d2aa9eedb9d07b72501d5964bf..10ce5eee8f0755d4bccc5891cd113b9cbbfddec1 100644 (file)
@@ -490,6 +490,15 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     |> Repo.one()
   end
 
+  def fetch_latest_undo(%User{ap_id: ap_id}) do
+    "Undo"
+    |> Activity.Queries.by_type()
+    |> where(actor: ^ap_id)
+    |> order_by([activity], fragment("? desc nulls last", activity.id))
+    |> limit(1)
+    |> Repo.one()
+  end
+
   def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do
     %{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id)
 
index ea6e79b441380d07934ffc7d0a1b001a893aec22..ce68e7d0e51d6cb3994464b53b223f4ceaa8e173 100644 (file)
@@ -1174,6 +1174,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       assert embedded_object["object"] == followed.ap_id
       assert embedded_object["id"] == follow_activity.data["id"]
     end
+
+    test "creates an undo activity for a pending follow request" do
+      follower = insert(:user)
+      followed = insert(:user, %{locked: true})
+
+      {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+      {:ok, activity} = ActivityPub.unfollow(follower, followed)
+
+      assert activity.data["type"] == "Undo"
+      assert activity.data["actor"] == follower.ap_id
+
+      embedded_object = activity.data["object"]
+      assert is_map(embedded_object)
+      assert embedded_object["type"] == "Follow"
+      assert embedded_object["object"] == followed.ap_id
+      assert embedded_object["id"] == follow_activity.data["id"]
+    end
   end
 
   describe "blocking / unblocking" do
index 214cbdd7cee31423970fb1ba904dc88cf65bcb8a..11f7c068f803b33d47aeaa06f9e65c3f039c7e0b 100644 (file)
@@ -551,6 +551,50 @@ defmodule Pleroma.Web.CommonAPITest do
 
       refute User.subscribed_to?(follower, followed)
     end
+
+    test "cancels a pending follow for a local user" do
+      follower = insert(:user)
+      followed = insert(:user, locked: true)
+
+      assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
+               CommonAPI.follow(follower, followed)
+
+      assert User.get_follow_state(follower, followed) == "pending"
+      assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
+      assert User.get_follow_state(follower, followed) == nil
+
+      assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
+               Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
+
+      assert %{
+               data: %{
+                 "type" => "Undo",
+                 "object" => %{"type" => "Follow", "state" => "cancelled"}
+               }
+             } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
+    end
+
+    test "cancels a pending follow for a remote user" do
+      follower = insert(:user)
+      followed = insert(:user, locked: true, local: false, ap_enabled: true)
+
+      assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
+               CommonAPI.follow(follower, followed)
+
+      assert User.get_follow_state(follower, followed) == "pending"
+      assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
+      assert User.get_follow_state(follower, followed) == nil
+
+      assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
+               Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
+
+      assert %{
+               data: %{
+                 "type" => "Undo",
+                 "object" => %{"type" => "Follow", "state" => "cancelled"}
+               }
+             } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
+    end
   end
 
   describe "accept_follow_request/2" do
index ec1e180025c03afc0aec36c782386cb2aa2b37a4..e2abcd7c5b36e84fab84054d851e6c01b8b9a6b7 100644 (file)
@@ -457,6 +457,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert id == to_string(other_user.id)
     end
 
+    test "cancelling follow request", %{conn: conn} do
+      %{id: other_user_id} = insert(:user, %{locked: true})
+
+      assert %{"id" => ^other_user_id, "following" => false, "requested" => true} =
+               conn |> post("/api/v1/accounts/#{other_user_id}/follow") |> json_response(:ok)
+
+      assert %{"id" => ^other_user_id, "following" => false, "requested" => false} =
+               conn |> post("/api/v1/accounts/#{other_user_id}/unfollow") |> json_response(:ok)
+    end
+
     test "following without reblogs" do
       %{conn: conn} = oauth_access(["follow", "read:statuses"])
       followed = insert(:user)