allow users to disable their own account
authorEgor Kislitsyn <egor@kislitsyn.com>
Mon, 4 Mar 2019 12:55:11 +0000 (19:55 +0700)
committerEgor Kislitsyn <egor@kislitsyn.com>
Mon, 4 Mar 2019 12:55:11 +0000 (19:55 +0700)
16 files changed:
config/config.exs
lib/mix/tasks/pleroma/user.ex
lib/pleroma/activity.ex
lib/pleroma/gopher/server.ex
lib/pleroma/notification.ex
lib/pleroma/user.ex
lib/pleroma/user/info.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/common_api/utils.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/controllers/util_controller.ex
lib/pleroma/web/twitter_api/twitter_api.ex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
priv/repo/migrations/20190228121252_users_add_disabled_index.exs [new file with mode: 0644]

index a620e74517c8f52f350e192d7ac80b2b66d46ad5..d9ed43dda63530e9230e92455ae5f574feee2b0e 100644 (file)
@@ -343,7 +343,8 @@ config :pleroma, Pleroma.Web.Federator.RetryQueue,
 config :pleroma, Pleroma.Jobs,
   federator_incoming: [max_jobs: 50],
   federator_outgoing: [max_jobs: 50],
-  mailer: [max_jobs: 10]
+  mailer: [max_jobs: 10],
+  user: [max_jobs: 10]
 
 config :auto_linker,
   opts: [
index 037e4471679d5f6ce780b80437964f49fa38c661..297332bc46aa6d2fa166f1fd5e595d8bb644f4fa 100644 (file)
@@ -23,7 +23,7 @@ defmodule Mix.Tasks.Pleroma.User do
   - `--password PASSWORD` - the user's password
   - `--moderator`/`--no-moderator` - whether the user is a moderator
   - `--admin`/`--no-admin` - whether the user is an admin
-  - `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions 
+  - `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
 
   ## Generate an invite link.
 
@@ -37,6 +37,10 @@ defmodule Mix.Tasks.Pleroma.User do
 
       mix pleroma.user toggle_activated NICKNAME
 
+  ## Disable or enable the user's account.
+
+      mix pleroma.user toggle_disabled NICKNAME
+
   ## Unsubscribe local users from user's account and deactivate it
 
       mix pleroma.user unsubscribe NICKNAME
@@ -170,6 +174,20 @@ defmodule Mix.Tasks.Pleroma.User do
     end
   end
 
+  def run(["toggle_disabled", nickname]) do
+    Common.start_pleroma()
+
+    case User.get_by_nickname(nickname) do
+      %User{} = user ->
+        {:ok, user} = User.disable(user, !user.info.disabled)
+        status = if(user.info.disabled, do: "ON", else: "OFF")
+        Mix.shell().info("Disabled status of #{nickname}: #{status}")
+
+      _ ->
+        Mix.shell().error("No user #{nickname}")
+    end
+  end
+
   def run(["reset_password", nickname]) do
     Common.start_pleroma()
 
index 66854dc2dd2920208261f4da1849420bd60c874a..c466bff7f0a834371fbb2a46299af6fb903f8285 100644 (file)
@@ -42,7 +42,10 @@ defmodule Pleroma.Activity do
   end
 
   def get_by_id(id) do
-    Repo.get(Activity, id)
+    Activity
+    |> where([a], a.id == ^id)
+    |> restrict_disabled_users()
+    |> Repo.one()
   end
 
   def by_object_ap_id(ap_id) do
@@ -92,6 +95,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_disabled_users()
     |> Repo.one()
   end
 
@@ -123,4 +127,14 @@ defmodule Pleroma.Activity do
     |> where([s], s.actor == ^actor)
     |> Repo.all()
   end
+
+  def restrict_disabled_users(query) do
+    from(activity in query,
+      where:
+        fragment(
+          "? not in (SELECT ap_id FROM users WHERE info->'disabled' @> 'true')",
+          activity.actor
+        )
+    )
+  end
 end
index ba9614029ce510b78c167d12109ecd340e00d88e..24190574e4e80b01a615583ef44f37a663f1c529 100644 (file)
@@ -41,7 +41,6 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
   alias Pleroma.Activity
   alias Pleroma.HTML
   alias Pleroma.User
-  alias Pleroma.Repo
 
   def start_link(ref, socket, transport, opts) do
     pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
@@ -110,7 +109,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
   end
 
   def response("/notices/" <> id) do
-    with %Activity{} = activity <- Repo.get(Activity, id),
+    with %Activity{} = activity <- Activity.get_by_id(id),
          true <- Visibility.is_public?(activity) do
       activities =
         ActivityPub.fetch_activities_for_context(activity.data["context"])
index c88512567bada8f768b6000bc18575afe3110d09..0f9f74b1ebacbea9f15b5d6f488c435bf11ef3d0 100644 (file)
@@ -36,22 +36,22 @@ defmodule Pleroma.Notification do
   defp restrict_since(query, _), do: query
 
   def for_user(user, opts \\ %{}) do
-    query =
-      from(
-        n in Notification,
-        where: n.user_id == ^user.id,
-        order_by: [desc: n.id],
-        join: activity in assoc(n, :activity),
-        preload: [activity: activity],
-        limit: 20
-      )
-
-    query =
-      query
-      |> restrict_since(opts)
-      |> restrict_max(opts)
-
-    Repo.all(query)
+    from(
+      n in Notification,
+      where: n.user_id == ^user.id,
+      order_by: [desc: n.id],
+      join: activity in assoc(n, :activity),
+      preload: [activity: activity],
+      limit: 20,
+      where:
+        fragment(
+          "? not in (SELECT ap_id FROM users WHERE info->'disabled' @> 'true')",
+          activity.actor
+        )
+    )
+    |> restrict_since(opts)
+    |> restrict_max(opts)
+    |> Repo.all()
   end
 
   def set_read_up_to(%{id: user_id} = _user, id) do
index 50e7e7ccd64f8a53877dcaf2cc25ae3be1463d4e..f02051174ac2d6909f3ae9ff3bb2ca62610cc368 100644 (file)
@@ -108,10 +108,8 @@ defmodule Pleroma.User do
   end
 
   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,
@@ -120,6 +118,23 @@ defmodule Pleroma.User do
     }
   end
 
+  defp restrict_disabled(query) do
+    from(u in query,
+      where: not fragment("? \\? 'disabled' AND ?->'disabled' @> '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_disabled()
+    |> Repo.aggregate(:count, :id)
+  end
+
   def remote_user_creation(params) do
     params =
       params
@@ -545,6 +560,7 @@ defmodule Pleroma.User do
       where: fragment("? <@ ?", ^[follower_address], u.following),
       where: u.id != ^id
     )
+    |> restrict_disabled()
   end
 
   def get_followers_query(user, page) do
@@ -572,6 +588,7 @@ defmodule Pleroma.User do
       where: u.follower_address in ^following,
       where: u.id != ^id
     )
+    |> restrict_disabled()
   end
 
   def get_friends_query(user, page) do
@@ -681,11 +698,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
@@ -694,6 +710,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_disabled()
 
     User
     |> where(id: ^user.id)
@@ -860,6 +877,7 @@ defmodule Pleroma.User do
           ^processed_query
         )
     )
+    |> restrict_disabled()
   end
 
   defp trigram_search_subquery(term) do
@@ -876,6 +894,7 @@ defmodule Pleroma.User do
       },
       where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
     )
+    |> restrict_disabled()
   end
 
   defp boost_search_results(results, nil), do: results
@@ -1062,11 +1081,10 @@ defmodule Pleroma.User do
   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)
-
-    update_and_set_cache(cng)
+    user
+    |> change()
+    |> put_embed(:info, info_cng)
+    |> update_and_set_cache()
   end
 
   def delete(%User{} = user) do
@@ -1100,6 +1118,26 @@ defmodule Pleroma.User do
     {:ok, user}
   end
 
+  def disable_async(user, status \\ true) do
+    Pleroma.Jobs.enqueue(:user, __MODULE__, [:disable_async, user, status])
+  end
+
+  def disable(%User{} = user, status \\ true) do
+    with {:ok, user} <- User.deactivate(user, status),
+         info_cng <- User.Info.set_disabled_status(user.info, status),
+         {:ok, user} <-
+           user
+           |> change()
+           |> put_embed(:info, info_cng)
+           |> update_and_set_cache(),
+         {:ok, friends} <- User.get_friends(user) do
+      Enum.each(friends, &update_follower_count(&1))
+      {:ok, user}
+    end
+  end
+
+  def perform(:disable_async, user, status), do: disable(user, status)
+
   def html_filter_policy(%User{info: %{no_rich_text: true}}) do
     Pleroma.HTML.Scrubber.TwitterText
   end
index 818b646454993b1e4fa472bfdbd3f2b692856a02..1ec356ba90af6eb5d3a282053a43bbb69313101c 100644 (file)
@@ -36,6 +36,7 @@ defmodule Pleroma.User.Info do
     field(:hide_follows, :boolean, default: false)
     field(:pinned_activities, {:array, :string}, default: [])
     field(:flavour, :string, default: nil)
+    field(:disabled, :boolean, default: false)
 
     # Found in the wild
     # ap_id -> Where is this used?
@@ -54,6 +55,14 @@ defmodule Pleroma.User.Info do
     |> validate_required([:deactivated])
   end
 
+  def set_disabled_status(info, disabled) do
+    params = %{disabled: disabled}
+
+    info
+    |> cast(params, [:disabled])
+    |> validate_required([:disabled])
+  end
+
   def add_to_note_count(info, number) do
     set_note_count(info, info.note_count + number)
   end
index 783491b67c7950ecd78f07880b22c986572f5c80..aa20990f3ddb4276446299235ba9e63fe3a4178a 100644 (file)
@@ -703,6 +703,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> restrict_replies(opts)
     |> restrict_reblogs(opts)
     |> restrict_pinned(opts)
+    |> Activity.restrict_disabled_users()
   end
 
   def fetch_activities(recipients, opts \\ %{}) do
index aae02cab8bdbe12f6b4f130895f08da5369b4f6b..1b94f0609d5d32dff77c896beb0e85225523c8c7 100644 (file)
@@ -44,6 +44,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     |> json(user.nickname)
   end
 
+  def user_toggle_disabled(conn, %{"nickname" => nickname}) do
+    user = User.get_by_nickname(nickname)
+
+    {:ok, updated_user} = User.disable(user, !user.info.disabled)
+
+    conn
+    |> put_view(AccountView)
+    |> render("show.json", %{user: updated_user})
+  end
+
   def user_toggle_activation(conn, %{"nickname" => nickname}) do
     user = User.get_by_nickname(nickname)
 
index e4b9102c50b968dc0751c70975559183af5dbb47..60d1185d3815efd7b2ea1fd3d80bcc47f96a8776 100644 (file)
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
 
   # This is a hack for twidere.
   def get_by_id_or_ap_id(id) do
-    activity = Repo.get(Activity, id) || Activity.get_create_by_object_ap_id(id)
+    activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id)
 
     activity &&
       if activity.data["type"] == "Create" do
@@ -30,7 +30,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
   def get_replied_to_activity(""), do: nil
 
   def get_replied_to_activity(id) when not is_nil(id) do
-    Repo.get(Activity, id)
+    Activity.get_by_id(id)
   end
 
   def get_replied_to_activity(_), do: nil
index 056be49b08e3f29751c4c95affcc6ecc8db4c104..00a0f1351fbea659d93368f9e514d4825e852e63 100644 (file)
@@ -307,7 +307,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Repo.get(Activity, id),
+    with %Activity{} = activity <- Activity.get_by_id(id),
          true <- Visibility.visible_for_user?(activity, user) do
       conn
       |> put_view(StatusView)
@@ -316,7 +316,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Repo.get(Activity, id),
+    with %Activity{} = activity <- Activity.get_by_id(id),
          activities <-
            ActivityPub.fetch_activities_for_context(activity.data["context"], %{
              "blocking_user" => user,
@@ -448,7 +448,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Repo.get(Activity, id),
+    with %Activity{} = activity <- Activity.get_by_id(id),
          %User{} = user <- User.get_by_nickname(user.nickname),
          true <- Visibility.visible_for_user?(activity, user),
          {:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do
@@ -459,7 +459,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Repo.get(Activity, id),
+    with %Activity{} = activity <- Activity.get_by_id(id),
          %User{} = user <- User.get_by_nickname(user.nickname),
          true <- Visibility.visible_for_user?(activity, user),
          {:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do
@@ -583,7 +583,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def favourited_by(conn, %{"id" => id}) do
-    with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
+    with %Activity{data: %{"object" => %{"likes" => likes}}} <- Activity.get_by_id(id) do
       q = from(u in User, where: u.ap_id in ^likes)
       users = Repo.all(q)
 
@@ -596,7 +596,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   end
 
   def reblogged_by(conn, %{"id" => id}) do
-    with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do
+    with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Activity.get_by_id(id) do
       q = from(u in User, where: u.ap_id in ^announces)
       users = Repo.all(q)
 
index 6fcb46878c56fe217ca41da52244fbcc93e226ad..5033b5446af44e4a41337efa0ef3555331fb3fd8 100644 (file)
@@ -143,6 +143,7 @@ defmodule Pleroma.Web.Router do
     get("/users/search", AdminAPIController, :search_users)
     delete("/user", AdminAPIController, :user_delete)
     patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
+    patch("/users/:nickname/toggle_disabled", AdminAPIController, :user_toggle_disabled)
     post("/user", AdminAPIController, :user_create)
     put("/users/tag", AdminAPIController, :tag_users)
     delete("/users/tag", AdminAPIController, :untag_users)
@@ -183,6 +184,7 @@ defmodule Pleroma.Web.Router do
 
       post("/change_password", UtilController, :change_password)
       post("/delete_account", UtilController, :delete_account)
+      post("/disable_account", UtilController, :disable_account)
     end
 
     scope [] do
index e2fdedb25f94cab9182a44726697741943f1fc44..0006d53e8d52b92d8d41351498dc2fc6debb6fd3 100644 (file)
@@ -311,6 +311,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.disable_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
index ab6470d7847443e060787d41970a12aefb6e49d0..615a34be92f87f51a9d2ee9952b92bab7ea54f41 100644 (file)
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
   end
 
   def delete(%User{} = user, id) do
-    with %Activity{data: %{"type" => _type}} <- Repo.get(Activity, id),
+    with %Activity{data: %{"type" => _type}} <- Activity.get_by_id(id),
          {:ok, activity} <- CommonAPI.delete(id, user) do
       {:ok, activity}
     end
@@ -232,21 +232,27 @@ 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: %{disabled: true}} ->
+            {:error, "User has been disabled"}
+
+          user ->
+            {:ok, user}
         end
 
       %{"screen_name" => nickname} ->
-        case target = Repo.get_by(User, nickname: nickname) do
+        case User.get_by_nickname(nickname) do
           nil ->
             {:error, "No user with such screen_name"}
 
-          _ ->
-            {:ok, target}
+          %User{info: %{disabled: true}} ->
+            {:error, "User has been disabled"}
+
+          user ->
+            {:ok, user}
         end
 
       _ ->
index de7b9f24ca29d986395dc4523ce97c0df5cfb5ce..0769f86989248e9e37eec5c0381b0a67c6367381 100644 (file)
@@ -269,7 +269,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
   end
 
   def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Activity{} = activity <- Repo.get(Activity, id),
+    with %Activity{} = activity <- Activity.get_by_id(id),
          true <- Visibility.visible_for_user?(activity, user) do
       conn
       |> put_view(ActivityView)
@@ -341,7 +341,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
   end
 
   def get_by_id_or_ap_id(id) do
-    activity = Repo.get(Activity, id) || Activity.get_create_by_object_ap_id(id)
+    activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id)
 
     if activity.data["type"] == "Create" do
       activity
diff --git a/priv/repo/migrations/20190228121252_users_add_disabled_index.exs b/priv/repo/migrations/20190228121252_users_add_disabled_index.exs
new file mode 100644 (file)
index 0000000..7b921d3
--- /dev/null
@@ -0,0 +1,7 @@
+defmodule Pleroma.Repo.Migrations.UsersAddDisabledIndex do
+  use Ecto.Migration
+
+  def change do
+    create(index(:users, ["(info->'disabled')"], name: :users_disabled_index, using: :gin))
+  end
+end