From 8c71f7e11a377d92234c141ea50170485e773fdc Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 4 Feb 2020 20:35:32 +0400 Subject: [PATCH 1/4] Add support for cancellation of a follow request --- lib/pleroma/user.ex | 25 +++++++++++-------- lib/pleroma/web/activity_pub/utils.ex | 9 +++++++ test/web/activity_pub/activity_pub_test.exs | 17 +++++++++++++ test/web/common_api/common_api_test.exs | 24 ++++++++++++++++++ .../controllers/account_controller_test.exs | 10 ++++++++ 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 3c86cdb38..398c91cf3 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -647,20 +647,25 @@ def follow(%User{} = follower, %User{} = followed, state \\ "accept") 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 FollowingRelationship.get(follower, followed) do + %{state: 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 diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 4f7fdaf38..5bca3868b 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -490,6 +490,15 @@ def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) 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) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index ff4604a52..c8f630266 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1174,6 +1174,23 @@ test "creates an undo activity for the last follow" 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 diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 8fa0c6faa..2bbe6c923 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -536,6 +536,30 @@ test "also unsubscribes a user" do refute User.subscribed_to?(follower, followed) end + + test "cancels a pending follow" do + follower = insert(:user) + followed = insert(:user, locked: true) + + assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = + CommonAPI.follow(follower, followed) + + assert %{state: "pending"} = Pleroma.FollowingRelationship.get(follower, followed) + + assert {:ok, follower} = CommonAPI.unfollow(follower, followed) + + assert Pleroma.FollowingRelationship.get(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 diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index ec1e18002..e2abcd7c5 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -457,6 +457,16 @@ test "following / unfollowing a user", %{conn: conn} 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) From 8b9742ecf546c37695229d54f0a0b3ed4edd66e1 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 6 Feb 2020 16:47:15 +0400 Subject: [PATCH 2/4] Cancellation of a follow request for a remote user --- lib/pleroma/following_relationship.ex | 23 ++++++++++++++++++---- test/web/common_api/common_api_test.exs | 26 ++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 0b0219b82..cc381af53 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -30,9 +30,24 @@ def changeset(%__MODULE__{} = following_relationship, attrs) do end def get(%User{} = follower, %User{} = following) do - __MODULE__ - |> where(follower_id: ^follower.id, following_id: ^following.id) - |> Repo.one() + following_relationship = + __MODULE__ + |> where(follower_id: ^follower.id, following_id: ^following.id) + |> Repo.one() + + case {following_relationship, following.local} do + {nil, false} -> + case Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, following) do + %{data: %{"state" => state}} when state in ["pending", "accept"] -> + %{state: state} + + _ -> + nil + end + + {following_relationship, _} -> + following_relationship + end end def update(follower, following, "reject"), do: unfollow(follower, following) @@ -58,8 +73,8 @@ def follow(%User{} = follower, %User{} = following, state \\ "accept") 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 diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 2bbe6c923..7eff24ce4 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -537,7 +537,7 @@ test "also unsubscribes a user" do refute User.subscribed_to?(follower, followed) end - test "cancels a pending follow" do + test "cancels a pending follow for a local user" do follower = insert(:user) followed = insert(:user, locked: true) @@ -560,6 +560,30 @@ test "cancels a pending follow" do } } = 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 %{state: "pending"} = Pleroma.FollowingRelationship.get(follower, followed) + + assert {:ok, follower} = CommonAPI.unfollow(follower, followed) + + assert Pleroma.FollowingRelationship.get(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 From 2cc4fbab96376f0ca815f9be0cfad8e5ba1df7ce Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 6 Feb 2020 18:03:53 +0400 Subject: [PATCH 3/4] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d0b3cecd..ecf84257b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,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
API Changes From bc2e98b20099be767a8262b734c6702edea663b4 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 7 Feb 2020 16:17:34 +0400 Subject: [PATCH 4/4] Add User.get_follow_state/2 --- lib/pleroma/following_relationship.ex | 21 +++------------------ lib/pleroma/user.ex | 22 ++++++++++++++++++++-- test/web/common_api/common_api_test.exs | 12 ++++-------- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index cc381af53..b8cb3bf03 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -30,24 +30,9 @@ def changeset(%__MODULE__{} = following_relationship, attrs) do end def get(%User{} = follower, %User{} = following) do - following_relationship = - __MODULE__ - |> where(follower_id: ^follower.id, following_id: ^following.id) - |> Repo.one() - - case {following_relationship, following.local} do - {nil, false} -> - case Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, following) do - %{data: %{"state" => state}} when state in ["pending", "accept"] -> - %{state: state} - - _ -> - nil - end - - {following_relationship, _} -> - following_relationship - end + __MODULE__ + |> where(follower_id: ^follower.id, following_id: ^following.id) + |> Repo.one() end def update(follower, following, "reject"), do: unfollow(follower, following) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 398c91cf3..5ea36fea3 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -652,8 +652,8 @@ def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do end def unfollow(%User{} = follower, %User{} = followed) do - case FollowingRelationship.get(follower, followed) do - %{state: state} when state in ["accept", "pending"] -> + case get_follow_state(follower, followed) do + state when state in ["accept", "pending"] -> FollowingRelationship.unfollow(follower, followed) {:ok, followed} = update_follower_count(followed) @@ -671,6 +671,24 @@ def unfollow(%User{} = follower, %User{} = followed) do 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 diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 7eff24ce4..a7b362525 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -544,11 +544,9 @@ test "cancels a pending follow for a local user" do assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = CommonAPI.follow(follower, followed) - assert %{state: "pending"} = Pleroma.FollowingRelationship.get(follower, followed) - + assert User.get_follow_state(follower, followed) == "pending" assert {:ok, follower} = CommonAPI.unfollow(follower, followed) - - assert Pleroma.FollowingRelationship.get(follower, followed) == nil + assert User.get_follow_state(follower, followed) == nil assert %{id: ^activity_id, data: %{"state" => "cancelled"}} = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed) @@ -568,11 +566,9 @@ test "cancels a pending follow for a remote user" do assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = CommonAPI.follow(follower, followed) - assert %{state: "pending"} = Pleroma.FollowingRelationship.get(follower, followed) - + assert User.get_follow_state(follower, followed) == "pending" assert {:ok, follower} = CommonAPI.unfollow(follower, followed) - - assert Pleroma.FollowingRelationship.get(follower, followed) == nil + assert User.get_follow_state(follower, followed) == nil assert %{id: ^activity_id, data: %{"state" => "cancelled"}} = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)