From 6e9a15b181fcca9e7485a61b1cce2e4ec6d46b78 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 28 Dec 2018 21:08:07 +0300 Subject: [PATCH 1/6] [#483] Blocked users export for TwitterAPI. --- lib/pleroma/user.ex | 3 +++ .../mastodon_api/mastodon_api_controller.ex | 6 ++---- lib/pleroma/web/router.ex | 1 + .../web/twitter_api/twitter_api_controller.ex | 8 ++++++++ .../twitter_api_controller_test.exs | 18 ++++++++++++++++++ 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 33f5e43fc..b64ed74c4 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -649,6 +649,9 @@ def blocks?(user, %{ap_id: ap_id}) do end) end + def blocked_users(user), + do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks)) + def block_domain(user, domain) do info_cng = user.info diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 22715bb76..663a0fa08 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -704,11 +704,9 @@ def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do end end - # TODO: Use proper query def blocks(%{assigns: %{user: user}} = conn, _) do - with blocked_users <- user.info.blocks || [], - accounts <- Enum.map(blocked_users, fn ap_id -> User.get_cached_by_ap_id(ap_id) end) do - res = AccountView.render("accounts.json", users: accounts, for: user, as: :user) + with blocked_accounts <- User.blocked_users(user) do + res = AccountView.render("accounts.json", users: blocked_accounts, for: user, as: :user) json(conn, res) end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 7ec0cabb3..a7f78ba81 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -281,6 +281,7 @@ defmodule Pleroma.Web.Router do get("/statuses/followers", TwitterAPI.Controller, :followers) get("/statuses/friends", TwitterAPI.Controller, :friends) + get("/statuses/blocks", TwitterAPI.Controller, :blocks) get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status) get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation) diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 7ae850d71..c11824afc 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -507,6 +507,14 @@ def friends(%{assigns: %{user: for_user}} = conn, params) do end end + def blocks(%{assigns: %{user: user}} = conn, _params) do + with blocked_users <- User.blocked_users(user) do + conn + |> put_view(UserView) + |> render("index.json", %{users: blocked_users, for: user}) + end + end + def friend_requests(conn, params) do with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params), {:ok, friend_requests} <- User.get_follow_requests(user) do diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs index 474d72df6..e49d605bd 100644 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -1085,6 +1085,24 @@ test "it returns the followers for a hidden network if requested by the user the end end + describe "GET /api/statuses/blocks" do + test "it returns the list of users blocked by requester", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, user} = User.block(user, other_user) + + conn = + conn + |> assign(:user, user) + |> get("/api/statuses/blocks") + + expected = UserView.render("index.json", %{users: [other_user], for: user}) + result = json_response(conn, 200) + assert Enum.sort(expected) == Enum.sort(result) + end + end + describe "GET /api/statuses/friends" do test "it returns the logged in user's friends", %{conn: conn} do user = insert(:user) From 700661b761117e6673ad254877ebba902b9d751b Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Fri, 28 Dec 2018 23:01:03 +0300 Subject: [PATCH 2/6] [#483] Blocked users list import (TwitterAPI). --- lib/pleroma/web/router.ex | 1 + .../controllers/util_controller.ex | 31 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index a7f78ba81..43b04e508 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -137,6 +137,7 @@ defmodule Pleroma.Web.Router do scope "/api/pleroma", Pleroma.Web.TwitterAPI do pipe_through(:authenticated_api) + post("/blocks_import", UtilController, :blocks_import) post("/follow_import", UtilController, :follow_import) post("/change_password", UtilController, :change_password) post("/delete_account", UtilController, :delete_account) diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index c872aec2b..6a9325afe 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -242,9 +242,12 @@ def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do def follow_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do Task.start(fn -> - String.split(list) + follower = User.get_cached_by_ap_id(user.ap_id) + + list + |> String.split() |> Enum.map(fn account -> - with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id), + with %User{} <- follower, %User{} = followed <- User.get_or_fetch(account), {:ok, follower} <- User.maybe_direct_follow(follower, followed) do ActivityPub.follow(follower, followed) @@ -257,6 +260,30 @@ def follow_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do json(conn, "job started") end + def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do + blocks_import(conn, %{"list" => File.read!(listfile.path)}) + end + + def blocks_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do + Task.start(fn -> + blocker = User.get_cached_by_ap_id(user.ap_id) + + list + |> String.split() + |> Enum.map(fn account -> + with %User{} <- blocker, + %User{} = blocked <- User.get_or_fetch(account), + {:ok, blocker} <- User.block(blocker, blocked) do + ActivityPub.block(blocker, blocked) + else + err -> Logger.debug("blocks_import: blocking #{account} failed with #{inspect(err)}") + end + end) + end) + + json(conn, "job started") + end + def change_password(%{assigns: %{user: user}} = conn, params) do case CommonAPI.Utils.confirm_current_password(user, params["password"]) do {:ok, user} -> From 67b4297f4d4010ee1b66452af4cea094d2cab2c4 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 29 Dec 2018 12:02:37 +0300 Subject: [PATCH 3/6] [#483] Refactored blocks and follows import, added tests. --- lib/pleroma/user.ex | 37 +++++++++++++++ .../controllers/util_controller.ex | 46 ++++--------------- test/user_test.exs | 30 ++++++++++++ test/web/twitter_api/util_controller_test.exs | 35 ++++++++++++++ 4 files changed, 112 insertions(+), 36 deletions(-) create mode 100644 test/web/twitter_api/util_controller_test.exs diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b64ed74c4..558014760 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -13,6 +13,8 @@ defmodule Pleroma.User do alias Pleroma.Web.{OStatus, Websub, OAuth} alias Pleroma.Web.ActivityPub.{Utils, ActivityPub} + require Logger + @type t :: %__MODULE__{} @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ @@ -331,6 +333,24 @@ def following?(%User{} = follower, %User{} = followed) do Enum.member?(follower.following, followed.follower_address) end + def follow_import(%User{} = follower, followed_identifiers) + when is_list(followed_identifiers) do + Enum.map( + followed_identifiers, + fn followed_identifier -> + with %User{} = followed <- get_or_fetch(followed_identifier), + {:ok, follower} <- maybe_direct_follow(follower, followed), + {:ok, _} <- ActivityPub.follow(follower, followed) do + followed + else + err -> + Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}") + err + end + end + ) + end + def locked?(%User{} = user) do user.info.locked || false end @@ -596,6 +616,23 @@ def search(query, resolve \\ false) do Repo.all(q) end + def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do + Enum.map( + blocked_identifiers, + fn blocked_identifier -> + with %User{} = blocked <- get_or_fetch(blocked_identifier), + {:ok, blocker} <- block(blocker, blocked), + {:ok, _} <- ActivityPub.block(blocker, blocked) do + blocked + else + err -> + Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}") + err + end + end + ) + end + def block(blocker, %User{ap_id: ap_id} = blocked) do # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213) blocker = diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 6a9325afe..87b8b71ba 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -240,48 +240,22 @@ def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do follow_import(conn, %{"list" => File.read!(listfile.path)}) end - def follow_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do - Task.start(fn -> - follower = User.get_cached_by_ap_id(user.ap_id) - - list - |> String.split() - |> Enum.map(fn account -> - with %User{} <- follower, - %User{} = followed <- User.get_or_fetch(account), - {:ok, follower} <- User.maybe_direct_follow(follower, followed) do - ActivityPub.follow(follower, followed) - else - err -> Logger.debug("follow_import: following #{account} failed with #{inspect(err)}") - end - end) - end) - - json(conn, "job started") + def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do + with followed_identifiers <- String.split(list), + {:ok, _} = Task.start(fn -> User.follow_import(follower, followed_identifiers) end) do + json(conn, "job started") + end end def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do blocks_import(conn, %{"list" => File.read!(listfile.path)}) end - def blocks_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do - Task.start(fn -> - blocker = User.get_cached_by_ap_id(user.ap_id) - - list - |> String.split() - |> Enum.map(fn account -> - with %User{} <- blocker, - %User{} = blocked <- User.get_or_fetch(account), - {:ok, blocker} <- User.block(blocker, blocked) do - ActivityPub.block(blocker, blocked) - else - err -> Logger.debug("blocks_import: blocking #{account} failed with #{inspect(err)}") - end - end) - end) - - json(conn, "job started") + def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do + with blocked_identifiers <- String.split(list), + {:ok, _} = Task.start(fn -> User.blocks_import(blocker, blocked_identifiers) end) do + json(conn, "job started") + end end def change_password(%{assigns: %{user: user}} = conn, params) do diff --git a/test/user_test.exs b/test/user_test.exs index 8c7e1594b..6a081c5c5 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -485,6 +485,21 @@ test "it sets the info->follower_count property" do end end + describe "follow_import" do + test "it imports user followings from list" do + [user1, user2, user3] = insert_list(3, :user) + + identifiers = [ + user2.ap_id, + user3.nickname + ] + + result = User.follow_import(user1, identifiers) + assert is_list(result) + assert result == [user2, user3] + end + end + describe "blocks" do test "it blocks people" do user = insert(:user) @@ -584,6 +599,21 @@ test "unblocks domains" do end end + describe "blocks_import" do + test "it imports user blocks from list" do + [user1, user2, user3] = insert_list(3, :user) + + identifiers = [ + user2.ap_id, + user3.nickname + ] + + result = User.blocks_import(user1, identifiers) + assert is_list(result) + assert result == [user2, user3] + end + end + test "get recipients from activity" do actor = insert(:user) user = insert(:user, local: true) diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs new file mode 100644 index 000000000..73aa70bd5 --- /dev/null +++ b/test/web/twitter_api/util_controller_test.exs @@ -0,0 +1,35 @@ +defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + + describe "POST /api/pleroma/follow_import" do + test "it returns HTTP 200", %{conn: conn} do + user1 = insert(:user) + user2 = insert(:user) + + response = + conn + |> assign(:user, user1) + |> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"}) + |> json_response(:ok) + + assert response == "job started" + end + end + + describe "POST /api/pleroma/blocks_import" do + test "it returns HTTP 200", %{conn: conn} do + user1 = insert(:user) + user2 = insert(:user) + + response = + conn + |> assign(:user, user1) + |> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"}) + |> json_response(:ok) + + assert response == "job started" + end + end +end From b3574dccbbb9d24ed90f0a82627d18428aaa7a16 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 29 Dec 2018 12:15:46 +0300 Subject: [PATCH 4/6] [#483] User.get_by_nickname/1: allowed retrieving user by fully-qualified local nickname (@). --- lib/pleroma/user.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 558014760..d4a6b13fb 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -387,7 +387,11 @@ def get_cached_by_nickname(nickname) do end def get_by_nickname(nickname) do - Repo.get_by(User, nickname: nickname) + Repo.get_by(User, nickname: nickname) || + if String.ends_with?(nickname, "@" <> Pleroma.Web.Endpoint.host()) do + [local_nickname, _] = String.split(nickname, "@") + Repo.get_by(User, nickname: local_nickname) + end end def get_by_nickname_or_email(nickname_or_email) do From 7bd49a32222045c34098f925fbd494461ab67ccd Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 29 Dec 2018 12:26:23 +0300 Subject: [PATCH 5/6] [#483] User.get_by_nickname/1: ensured case-insensitive matching for local FQN. Added tests. --- lib/pleroma/user.ex | 2 +- test/user_test.exs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d4a6b13fb..1f6d4cc5e 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -388,7 +388,7 @@ def get_cached_by_nickname(nickname) do def get_by_nickname(nickname) do Repo.get_by(User, nickname: nickname) || - if String.ends_with?(nickname, "@" <> Pleroma.Web.Endpoint.host()) do + if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do [local_nickname, _] = String.split(nickname, "@") Repo.get_by(User, nickname: local_nickname) end diff --git a/test/user_test.exs b/test/user_test.exs index 6a081c5c5..8225453ab 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -278,6 +278,25 @@ test "gets an existing user, case insensitive" do assert user == fetched_user end + + test "gets an existing user by fully qualified nickname" do + user = insert(:user) + + fetched_user = + User.get_or_fetch_by_nickname(user.nickname <> "@" <> Pleroma.Web.Endpoint.host()) + + assert user == fetched_user + end + + test "gets an existing user by fully qualified nickname, case insensitive" do + user = insert(:user, nickname: "nick") + casing_altered_fqn = String.upcase(user.nickname <> "@" <> Pleroma.Web.Endpoint.host()) + + fetched_user = User.get_or_fetch_by_nickname(casing_altered_fqn) + + assert user == fetched_user + end + test "fetches an external user via ostatus if no user exists" do fetched_user = User.get_or_fetch_by_nickname("shp@social.heldscal.la") assert fetched_user.nickname == "shp@social.heldscal.la" From 242cc9a6589f54c523284dc2ec18990feb2ca00a Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 29 Dec 2018 12:26:23 +0300 Subject: [PATCH 6/6] [#483] User.get_by_nickname/1: ensured case-insensitive matching for local FQN. Added tests. --- test/user_test.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/user_test.exs b/test/user_test.exs index 8225453ab..4680850ea 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -278,7 +278,6 @@ test "gets an existing user, case insensitive" do assert user == fetched_user end - test "gets an existing user by fully qualified nickname" do user = insert(:user)