Merge branch 'bugfix/377-stuck-follow-request' into 'develop'

Bugfix/377 stuck follow request

Closes #377

See merge request pleroma/pleroma!1250
This commit is contained in:
kaniini 2019-06-05 15:52:40 +00:00
commit 3f6da9c45a
9 changed files with 143 additions and 35 deletions

View file

@ -90,6 +90,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Respond with a 404 Not implemented JSON error message when requested API is not implemented - Respond with a 404 Not implemented JSON error message when requested API is not implemented
### Fixed ### Fixed
- Follow requests don't get 'stuck' anymore.
- Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended. - Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended.
- Followers counter not being updated when a follower is blocked - Followers counter not being updated when a follower is blocked
- Deactivated users being able to request an access token - Deactivated users being able to request an access token

View file

@ -324,14 +324,6 @@ defmodule Pleroma.User do
end end
end end
def maybe_follow(%User{} = follower, %User{info: _info} = followed) do
if not following?(follower, followed) do
follow(follower, followed)
else
{:ok, follower}
end
end
@doc "A mass follow for local users. Respects blocks in both directions but does not create activities." @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
@spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()} @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
def follow_all(follower, followeds) do def follow_all(follower, followeds) do

View file

@ -458,10 +458,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower), {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]), with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
{:user_blocked, false} <- {_, false} <-
{:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked}, {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
{:user_locked, false} <- {:user_locked, User.locked?(followed)}, {_, false} <- {:user_locked, User.locked?(followed)},
{:follow, {:ok, follower}} <- {:follow, User.follow(follower, followed)} do {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
{_, {:ok, _}} <-
{:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")} do
ActivityPub.accept(%{ ActivityPub.accept(%{
to: [follower.ap_id], to: [follower.ap_id],
actor: followed, actor: followed,
@ -470,7 +472,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
}) })
else else
{:user_blocked, true} -> {:user_blocked, true} ->
{:ok, _} = Utils.update_follow_state(activity, "reject") {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
ActivityPub.reject(%{ ActivityPub.reject(%{
to: [follower.ap_id], to: [follower.ap_id],
@ -480,7 +482,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
}) })
{:follow, {:error, _}} -> {:follow, {:error, _}} ->
{:ok, _} = Utils.update_follow_state(activity, "reject") {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
ActivityPub.reject(%{ ActivityPub.reject(%{
to: [follower.ap_id], to: [follower.ap_id],
@ -506,21 +508,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with actor <- Containment.get_actor(data), with actor <- Containment.get_actor(data),
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor), {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
{:ok, follow_activity} <- get_follow_activity(follow_object, followed), {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, activity} <- {:ok, _follower} = User.follow(follower, followed) do
ActivityPub.accept(%{ ActivityPub.accept(%{
to: follow_activity.data["to"], to: follow_activity.data["to"],
type: "Accept", type: "Accept",
actor: followed, actor: followed,
object: follow_activity.data["id"], object: follow_activity.data["id"],
local: false local: false
}) do })
if not User.following?(follower, followed) do
{:ok, _follower} = User.follow(follower, followed)
end
{:ok, activity}
else else
_e -> :error _e -> :error
end end
@ -532,7 +529,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with actor <- Containment.get_actor(data), with actor <- Containment.get_actor(data),
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor), {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
{:ok, follow_activity} <- get_follow_activity(follow_object, followed), {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, activity} <- {:ok, activity} <-
ActivityPub.reject(%{ ActivityPub.reject(%{

View file

@ -376,8 +376,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
@doc """ @doc """
Updates a follow activity's state (for locked accounts). Updates a follow activity's state (for locked accounts).
""" """
def update_follow_state( def update_follow_state_for_all(
%Activity{data: %{"actor" => actor, "object" => object, "state" => "pending"}} = activity, %Activity{data: %{"actor" => actor, "object" => object}} = activity,
state state
) do ) do
try do try do

View file

@ -35,9 +35,9 @@ defmodule Pleroma.Web.CommonAPI do
end end
def accept_follow_request(follower, followed) do def accept_follow_request(follower, followed) do
with {:ok, follower} <- User.maybe_follow(follower, followed), with {:ok, follower} <- User.follow(follower, followed),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
{:ok, _activity} <- {:ok, _activity} <-
ActivityPub.accept(%{ ActivityPub.accept(%{
to: [follower.ap_id], to: [follower.ap_id],
@ -51,7 +51,7 @@ defmodule Pleroma.Web.CommonAPI do
def reject_follow_request(follower, followed) do def reject_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
{:ok, _activity} <- {:ok, _activity} <-
ActivityPub.reject(%{ ActivityPub.reject(%{
to: [follower.ap_id], to: [follower.ap_id],

View file

@ -75,7 +75,7 @@ defmodule Pleroma.UserTest do
Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id}) Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id}) Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
Pleroma.Web.TwitterAPI.TwitterAPI.follow(accepted_follower, %{"user_id" => locked.id}) Pleroma.Web.TwitterAPI.TwitterAPI.follow(accepted_follower, %{"user_id" => locked.id})
User.maybe_follow(accepted_follower, locked) User.follow(accepted_follower, locked)
assert {:ok, [activity]} = User.get_follow_requests(locked) assert {:ok, [activity]} = User.get_follow_requests(locked)
assert activity assert activity

View file

@ -27,14 +27,41 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do
|> Poison.decode!() |> Poison.decode!()
|> Map.put("object", user.ap_id) |> Map.put("object", user.ap_id)
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
assert data["actor"] == "http://mastodon.example.org/users/admin" assert data["actor"] == "http://mastodon.example.org/users/admin"
assert data["type"] == "Follow" assert data["type"] == "Follow"
assert data["id"] == "http://mastodon.example.org/users/admin#follows/2" assert data["id"] == "http://mastodon.example.org/users/admin#follows/2"
activity = Repo.get(Activity, activity.id)
assert activity.data["state"] == "accept"
assert User.following?(User.get_cached_by_ap_id(data["actor"]), user) assert User.following?(User.get_cached_by_ap_id(data["actor"]), user)
end end
test "with locked accounts, it does not create a follow or an accept" do
user = insert(:user, info: %{locked: true})
data =
File.read!("test/fixtures/mastodon-follow-activity.json")
|> Poison.decode!()
|> Map.put("object", user.ap_id)
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
assert data["state"] == "pending"
refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
accepts =
from(
a in Activity,
where: fragment("?->>'type' = ?", a.data, "Accept")
)
|> Repo.all()
assert length(accepts) == 0
end
test "it works for follow requests when you are already followed, creating a new accept activity" do test "it works for follow requests when you are already followed, creating a new accept activity" do
# This is important because the remote might have the wrong idea about the # This is important because the remote might have the wrong idea about the
# current follow status. This can lead to instance A thinking that x@A is # current follow status. This can lead to instance A thinking that x@A is

View file

@ -2,6 +2,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
use Pleroma.DataCase use Pleroma.DataCase
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
@ -247,4 +248,51 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
assert fetched_vote.id == vote.id assert fetched_vote.id == vote.id
end end
end end
describe "update_follow_state_for_all/2" do
test "updates the state of all Follow activities with the same actor and object" do
user = insert(:user, info: %{locked: true})
follower = insert(:user)
{:ok, follow_activity} = ActivityPub.follow(follower, user)
{:ok, follow_activity_two} = ActivityPub.follow(follower, user)
data =
follow_activity_two.data
|> Map.put("state", "accept")
cng = Ecto.Changeset.change(follow_activity_two, data: data)
{:ok, follow_activity_two} = Repo.update(cng)
{:ok, follow_activity_two} =
Utils.update_follow_state_for_all(follow_activity_two, "accept")
assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
end
end
describe "update_follow_state/2" do
test "updates the state of the given follow activity" do
user = insert(:user, info: %{locked: true})
follower = insert(:user)
{:ok, follow_activity} = ActivityPub.follow(follower, user)
{:ok, follow_activity_two} = ActivityPub.follow(follower, user)
data =
follow_activity_two.data
|> Map.put("state", "accept")
cng = Ecto.Changeset.change(follow_activity_two, data: data)
{:ok, follow_activity_two} = Repo.update(cng)
{:ok, follow_activity_two} = Utils.update_follow_state(follow_activity_two, "reject")
assert Repo.get(Activity, follow_activity.id).data["state"] == "pending"
assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
end
end
end end

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.CommonAPITest do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
import Pleroma.Factory import Pleroma.Factory
@ -339,4 +340,46 @@ defmodule Pleroma.Web.CommonAPITest do
assert User.showing_reblogs?(muter, muted) == true assert User.showing_reblogs?(muter, muted) == true
end end
end end
describe "accept_follow_request/2" do
test "after acceptance, it sets all existing pending follow request states to 'accept'" do
user = insert(:user, info: %{locked: true})
follower = insert(:user)
follower_two = insert(:user)
{:ok, follow_activity} = ActivityPub.follow(follower, user)
{:ok, follow_activity_two} = ActivityPub.follow(follower, user)
{:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
assert follow_activity.data["state"] == "pending"
assert follow_activity_two.data["state"] == "pending"
assert follow_activity_three.data["state"] == "pending"
{:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
end
test "after rejection, it sets all existing pending follow request states to 'reject'" do
user = insert(:user, info: %{locked: true})
follower = insert(:user)
follower_two = insert(:user)
{:ok, follow_activity} = ActivityPub.follow(follower, user)
{:ok, follow_activity_two} = ActivityPub.follow(follower, user)
{:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
assert follow_activity.data["state"] == "pending"
assert follow_activity_two.data["state"] == "pending"
assert follow_activity_three.data["state"] == "pending"
{:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
end
end
end end