AP follows must be always async (closes #306) Closes #306 See merge request pleroma/pleroma!368tags/v0.9.9
@@ -109,7 +109,8 @@ config :pleroma, :fe, | |||||
config :pleroma, :activitypub, | config :pleroma, :activitypub, | ||||
accept_blocks: true, | accept_blocks: true, | ||||
unfollow_blocked: true, | unfollow_blocked: true, | ||||
outgoing_blocks: true | |||||
outgoing_blocks: true, | |||||
follow_handshake_timeout: 500 | |||||
config :pleroma, :user, deny_follow_blocked: true | config :pleroma, :user, deny_follow_blocked: true | ||||
@@ -185,32 +185,7 @@ defmodule Pleroma.User do | |||||
def needs_update?(_), do: true | def needs_update?(_), do: true | ||||
def maybe_direct_follow(%User{} = follower, %User{info: info} = followed) do | def maybe_direct_follow(%User{} = follower, %User{info: info} = followed) do | ||||
user_config = Application.get_env(:pleroma, :user) | |||||
deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked) | |||||
user_info = user_info(followed) | |||||
should_direct_follow = | |||||
cond do | |||||
# if the account is locked, don't pre-create the relationship | |||||
user_info[:locked] == true -> | |||||
false | |||||
# if the users are blocking each other, we shouldn't even be here, but check for it anyway | |||||
deny_follow_blocked and | |||||
(User.blocks?(follower, followed) or User.blocks?(followed, follower)) -> | |||||
false | |||||
# if OStatus, then there is no three-way handshake to follow | |||||
User.ap_enabled?(followed) != true -> | |||||
true | |||||
# if there are no other reasons not to, just pre-create the relationship | |||||
true -> | |||||
true | |||||
end | |||||
if should_direct_follow do | |||||
if !User.ap_enabled?(followed) do | |||||
follow(follower, followed) | follow(follower, followed) | ||||
else | else | ||||
{:ok, follower} | {:ok, follower} | ||||
@@ -763,4 +738,28 @@ defmodule Pleroma.User do | |||||
get_or_fetch_by_nickname(uri_or_nickname) | get_or_fetch_by_nickname(uri_or_nickname) | ||||
end | end | ||||
end | end | ||||
# wait a period of time and return newest version of the User structs | |||||
# this is because we have synchronous follow APIs and need to simulate them | |||||
# with an async handshake | |||||
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do | |||||
with %User{} = a <- Repo.get(User, a.id), | |||||
%User{} = b <- Repo.get(User, b.id) do | |||||
{:ok, a, b} | |||||
else | |||||
_e -> | |||||
:error | |||||
end | |||||
end | |||||
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do | |||||
with :ok <- :timer.sleep(timeout), | |||||
%User{} = a <- Repo.get(User, a.id), | |||||
%User{} = b <- Repo.get(User, b.id) do | |||||
{:ok, a, b} | |||||
else | |||||
_e -> | |||||
:error | |||||
end | |||||
end | |||||
end | end |
@@ -326,6 +326,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||||
with actor <- get_actor(data), | with actor <- get_actor(data), | ||||
%User{} = followed <- User.get_or_fetch_by_ap_id(actor), | %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"), | |||||
%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.accept(%{ | ActivityPub.accept(%{ | ||||
@@ -351,6 +352,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||||
with actor <- get_actor(data), | with actor <- get_actor(data), | ||||
%User{} = followed <- User.get_or_fetch_by_ap_id(actor), | %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"), | |||||
%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.accept(%{ | ActivityPub.accept(%{ | ||||
@@ -247,11 +247,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||||
"actor" => follower_id, | "actor" => follower_id, | ||||
"to" => [followed_id], | "to" => [followed_id], | ||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"], | "cc" => ["https://www.w3.org/ns/activitystreams#Public"], | ||||
"object" => followed_id | |||||
"object" => followed_id, | |||||
"state" => "pending" | |||||
} | } | ||||
data = if activity_id, do: Map.put(data, "id", activity_id), else: data | data = if activity_id, do: Map.put(data, "id", activity_id), else: data | ||||
data = if User.locked?(followed), do: Map.put(data, "state", "pending"), else: data | |||||
data | data | ||||
end | end | ||||
@@ -571,10 +571,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
end | end | ||||
end | end | ||||
@activitypub Application.get_env(:pleroma, :activitypub) | |||||
@follow_handshake_timeout Keyword.get(@activitypub, :follow_handshake_timeout) | |||||
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do | def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do | ||||
with %User{} = followed <- Repo.get(User, id), | with %User{} = followed <- Repo.get(User, id), | ||||
{:ok, follower} <- User.maybe_direct_follow(follower, followed), | {:ok, follower} <- User.maybe_direct_follow(follower, followed), | ||||
{:ok, _activity} <- ActivityPub.follow(follower, followed) do | |||||
{:ok, _activity} <- ActivityPub.follow(follower, followed), | |||||
{:ok, follower, followed} <- | |||||
User.wait_and_refresh(@follow_handshake_timeout, follower, followed) do | |||||
render(conn, AccountView, "relationship.json", %{user: follower, target: followed}) | render(conn, AccountView, "relationship.json", %{user: follower, target: followed}) | ||||
else | else | ||||
{:error, message} -> | {:error, message} -> | ||||
@@ -72,6 +72,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do | |||||
end | end | ||||
def render("relationship.json", %{user: user, target: target}) do | def render("relationship.json", %{user: user, target: target}) do | ||||
follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target) | |||||
requested = | |||||
if follow_activity do | |||||
follow_activity.data["state"] == "pending" | |||||
else | |||||
false | |||||
end | |||||
%{ | %{ | ||||
id: to_string(target.id), | id: to_string(target.id), | ||||
following: User.following?(user, target), | following: User.following?(user, target), | ||||
@@ -79,7 +88,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do | |||||
blocking: User.blocks?(user, target), | blocking: User.blocks?(user, target), | ||||
muting: false, | muting: false, | ||||
muting_notifications: false, | muting_notifications: false, | ||||
requested: false, | |||||
requested: requested, | |||||
domain_blocking: false, | domain_blocking: false, | ||||
showing_reblogs: false, | showing_reblogs: false, | ||||
endorsed: false | endorsed: false | ||||
@@ -20,10 +20,15 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do | |||||
end | end | ||||
end | end | ||||
@activitypub Application.get_env(:pleroma, :activitypub) | |||||
@follow_handshake_timeout Keyword.get(@activitypub, :follow_handshake_timeout) | |||||
def follow(%User{} = follower, params) do | def follow(%User{} = follower, params) do | ||||
with {:ok, %User{} = followed} <- get_user(params), | with {:ok, %User{} = followed} <- get_user(params), | ||||
{:ok, follower} <- User.maybe_direct_follow(follower, followed), | {:ok, follower} <- User.maybe_direct_follow(follower, followed), | ||||
{:ok, activity} <- ActivityPub.follow(follower, followed) do | |||||
{:ok, activity} <- ActivityPub.follow(follower, followed), | |||||
{:ok, follower, followed} <- | |||||
User.wait_and_refresh(@follow_handshake_timeout, follower, followed) do | |||||
{:ok, follower, followed, activity} | {:ok, follower, followed, activity} | ||||
else | else | ||||
err -> err | err -> err | ||||