From: Egor Kislitsyn Date: Mon, 22 Apr 2019 06:08:30 +0000 (+0700) Subject: Merge remote-tracking branch 'pleroma/develop' into feature/disable-account X-Git-Url: http://git.squeep.com/?a=commitdiff_plain;h=e8c2f9a73a37636a9a8ed5c2998617b841f482da;hp=7fcbda702e76b6390076c28832f5aea80086d15a;p=akkoma Merge remote-tracking branch 'pleroma/develop' into feature/disable-account --- diff --git a/config/config.exs b/config/config.exs index 9f2244222..9dc9387c8 100644 --- a/config/config.exs +++ b/config/config.exs @@ -413,7 +413,8 @@ config :pleroma_job_queue, :queues, web_push: 50, mailer: 10, transmogrifier: 20, - scheduled_activities: 10 + scheduled_activities: 10, + user: 10 config :pleroma, :fetch_initial_posts, enabled: false, diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index dbe250300..b9622f586 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -61,6 +61,15 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi * Response: JSON. Returns `{"status": "success"}` if the deletion was successful, `{"error": "[error message]"}` otherwise * Example response: `{"error": "Invalid password."}` +## `/api/pleroma/disable_account` +### Disable an account +* Method `POST` +* Authentication: required +* Params: + * `password`: user's password +* Response: JSON. Returns `{"status": "success"}` if the account was successfully disabled, `{"error": "[error message]"}` otherwise +* Example response: `{"error": "Invalid password."}` + ## `/api/account/register` ### Register a new user * Method `POST` diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 4a2ded518..9c1c804e0 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -106,7 +106,10 @@ defmodule Pleroma.Activity do end def get_by_id(id) do - Repo.get(Activity, id) + Activity + |> where([a], a.id == ^id) + |> restrict_deactivated_users() + |> Repo.one() end def get_by_id_with_object(id) do @@ -174,6 +177,7 @@ defmodule Pleroma.Activity do def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do create_by_object_ap_id(ap_id) + |> restrict_deactivated_users() |> Repo.one() end @@ -260,4 +264,14 @@ defmodule Pleroma.Activity do |> where([s], s.actor == ^actor) |> Repo.all() end + + def restrict_deactivated_users(query) do + from(activity in query, + where: + fragment( + "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')", + activity.actor + ) + ) + end end diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index b357d5399..585157efe 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -33,6 +33,13 @@ defmodule Pleroma.Notification do def for_user_query(user) do Notification |> where(user_id: ^user.id) + |> where( + [n, a], + fragment( + "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')", + a.actor + ) + ) |> join(:inner, [n], activity in assoc(n, :activity)) |> join(:left, [n, a], object in Object, on: diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 78eb29ddd..6aaa3244f 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -107,10 +107,8 @@ defmodule Pleroma.User do def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers" def user_info(%User{} = user) do - oneself = if user.local, do: 1, else: 0 - %{ - following_count: length(user.following) - oneself, + following_count: following_count(user), note_count: user.info.note_count, follower_count: user.info.follower_count, locked: user.info.locked, @@ -119,6 +117,23 @@ defmodule Pleroma.User do } end + defp restrict_deactivated(query) do + from(u in query, + where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info) + ) + end + + def following_count(%User{following: []}), do: 0 + + def following_count(%User{following: following, id: id}) do + from(u in User, + where: u.follower_address in ^following, + where: u.id != ^id + ) + |> restrict_deactivated() + |> Repo.aggregate(:count, :id) + end + def remote_user_creation(params) do params = params @@ -571,6 +586,7 @@ defmodule Pleroma.User do where: fragment("? <@ ?", ^[follower_address], u.following), where: u.id != ^id ) + |> restrict_deactivated() end def get_followers_query(user, page) do @@ -598,6 +614,7 @@ defmodule Pleroma.User do where: u.follower_address in ^following, where: u.id != ^id ) + |> restrict_deactivated() end def get_friends_query(user, page) do @@ -709,11 +726,10 @@ defmodule Pleroma.User do info_cng = User.Info.set_note_count(user.info, note_count) - cng = - change(user) - |> put_embed(:info, info_cng) - - update_and_set_cache(cng) + user + |> change() + |> put_embed(:info, info_cng) + |> update_and_set_cache() end def update_follower_count(%User{} = user) do @@ -722,6 +738,7 @@ defmodule Pleroma.User do |> where([u], ^user.follower_address in u.following) |> where([u], u.id != ^user.id) |> select([u], %{count: count(u.id)}) + |> restrict_deactivated() User |> where(id: ^user.id) @@ -872,6 +889,7 @@ defmodule Pleroma.User do ^processed_query ) ) + |> restrict_deactivated() end defp trigram_search_subquery(term) do @@ -890,6 +908,7 @@ defmodule Pleroma.User do }, where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term) ) + |> restrict_deactivated() end def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do @@ -1133,14 +1152,27 @@ defmodule Pleroma.User do ) end + def deactivate_async(user, status \\ true) do + PleromaJobQueue.enqueue(:user, __MODULE__, [:deactivate_async, user, status]) + end + + def perform(:deactivate_async, user, status), do: deactivate(user, status) + def deactivate(%User{} = user, status \\ true) do info_cng = User.Info.set_activation_status(user.info, status) - cng = - change(user) - |> put_embed(:info, info_cng) + with {:ok, friends} <- User.get_friends(user), + {:ok, followers} <- User.get_followers(user), + {:ok, user} <- + user + |> change() + |> put_embed(:info, info_cng) + |> update_and_set_cache() do + Enum.each(followers, &invalidate_cache(&1)) + Enum.each(friends, &update_follower_count(&1)) - update_and_set_cache(cng) + {:ok, user} + end end def update_notification_settings(%User{} = user, settings \\ %{}) do diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index e77b2b72d..a345372e2 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -805,6 +805,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_reblogs(opts) |> restrict_pinned(opts) |> restrict_muted_reblogs(opts) + |> Activity.restrict_deactivated_users() end def fetch_activities(recipients, opts \\ %{}) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8b665d61b..f475de639 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -196,6 +196,7 @@ defmodule Pleroma.Web.Router do post("/change_password", UtilController, :change_password) post("/delete_account", UtilController, :delete_account) put("/notification_settings", UtilController, :update_notificaton_settings) + post("/disable_account", UtilController, :disable_account) end scope [] do diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 8665e058a..9b0cf2b07 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -360,6 +360,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end + def disable_account(%{assigns: %{user: user}} = conn, params) do + case CommonAPI.Utils.confirm_current_password(user, params["password"]) do + {:ok, user} -> + User.deactivate_async(user) + json(conn, %{status: "success"}) + + {:error, msg} -> + json(conn, %{error: msg}) + end + end + def captcha(conn, _params) do json(conn, Pleroma.Captcha.new()) end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 8e44dbeb8..c3f769c00 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -231,12 +231,15 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do def get_user(user \\ nil, params) do case params do %{"user_id" => user_id} -> - case target = User.get_cached_by_nickname_or_id(user_id) do + case User.get_cached_by_nickname_or_id(user_id) do nil -> {:error, "No user with such user_id"} - _ -> - {:ok, target} + %User{info: %{deactivated: true}} -> + {:error, "User has been disabled"} + + user -> + {:ok, user} end %{"screen_name" => nickname} -> diff --git a/priv/repo/migrations/20190411094120_add_index_on_user_info_deactivated.exs b/priv/repo/migrations/20190411094120_add_index_on_user_info_deactivated.exs new file mode 100644 index 000000000..d701dcecc --- /dev/null +++ b/priv/repo/migrations/20190411094120_add_index_on_user_info_deactivated.exs @@ -0,0 +1,7 @@ +defmodule Pleroma.Repo.Migrations.AddIndexOnUserInfoDeactivated do + use Ecto.Migration + + def change do + create(index(:users, ["(info->'deactivated')"], name: :users_deactivated_index, using: :gin)) + end +end diff --git a/test/user_test.exs b/test/user_test.exs index eee6881eb..a5f931853 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -8,6 +8,7 @@ defmodule Pleroma.UserTest do alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI use Pleroma.DataCase @@ -213,8 +214,8 @@ defmodule Pleroma.UserTest do test "fetches correct profile for nickname beginning with number" do # Use old-style integer ID to try to reproduce the problem user = insert(:user, %{id: 1080}) - userwithnumbers = insert(:user, %{nickname: "#{user.id}garbage"}) - assert userwithnumbers == User.get_cached_by_nickname_or_id(userwithnumbers.nickname) + user_with_numbers = insert(:user, %{nickname: "#{user.id}garbage"}) + assert user_with_numbers == User.get_cached_by_nickname_or_id(user_with_numbers.nickname) end describe "user registration" do @@ -816,13 +817,71 @@ defmodule Pleroma.UserTest do assert addressed in recipients end - test ".deactivate can de-activate then re-activate a user" do - user = insert(:user) - assert false == user.info.deactivated - {:ok, user} = User.deactivate(user) - assert true == user.info.deactivated - {:ok, user} = User.deactivate(user, false) - assert false == user.info.deactivated + describe ".deactivate" do + test "can de-activate then re-activate a user" do + user = insert(:user) + assert false == user.info.deactivated + {:ok, user} = User.deactivate(user) + assert true == user.info.deactivated + {:ok, user} = User.deactivate(user, false) + assert false == user.info.deactivated + end + + test "hide a user from followers " do + user = insert(:user) + user2 = insert(:user) + + {:ok, user} = User.follow(user, user2) + {:ok, _user} = User.deactivate(user) + + info = User.get_cached_user_info(user2) + + assert info.follower_count == 0 + assert {:ok, []} = User.get_followers(user2) + end + + test "hide a user from friends" do + user = insert(:user) + user2 = insert(:user) + + {:ok, user2} = User.follow(user2, user) + assert User.following_count(user2) == 1 + + {:ok, _user} = User.deactivate(user) + + info = User.get_cached_user_info(user2) + + assert info.following_count == 0 + assert User.following_count(user2) == 0 + assert {:ok, []} = User.get_friends(user2) + end + + test "hide a user's statuses from timelines and notifications" do + user = insert(:user) + user2 = insert(:user) + + {:ok, user2} = User.follow(user2, user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{user2.nickname}"}) + + [notification] = Pleroma.Notification.for_user(user2) + assert notification.activity.id == activity.id + + assert [activity] == ActivityPub.fetch_public_activities(%{}) + + assert [activity] == + ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2}) + |> ActivityPub.contain_timeline(user2) + + {:ok, _user} = User.deactivate(user) + + assert [] == ActivityPub.fetch_public_activities(%{}) + assert [] == Pleroma.Notification.for_user(user2) + + assert [] == + ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2}) + |> ActivityPub.contain_timeline(user2) + end end test ".delete_user_activities deletes all create activities" do diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index c58b49ea4..0288e24d1 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -245,4 +245,22 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do assert html_response(response, 200) =~ "Log in to follow" end end + + describe "POST /api/pleroma/disable_account" do + test "it returns HTTP 200", %{conn: conn} do + user = insert(:user) + + response = + conn + |> assign(:user, user) + |> post("/api/pleroma/disable_account", %{"password" => "test"}) + |> json_response(:ok) + + assert response == %{"status" => "success"} + + user = User.get_cached_by_id(user.id) + + assert user.info.deactivated == true + end + end end