aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkaniini <nenolod@gmail.com>2019-05-14 12:06:08 +0000
committerkaniini <nenolod@gmail.com>2019-05-14 12:06:08 +0000
commit4e69d1239afdf97fe84a1772faa242b8e362b369 (patch)
treeb37250ceeea947d284cbff32fa5665a3bbdd15f9
parentcdcdbd88da76f18c21da7f6f15a29883044902c8 (diff)
parentc1665fd94de456768ddd59b8873d1bd26878970d (diff)
downloadpleroma-4e69d1239afdf97fe84a1772faa242b8e362b369.tar.gz
Merge branch 'feature/disable-account' into 'develop'
[#694] allow users to disable their own account See merge request pleroma/pleroma!895
-rw-r--r--CHANGELOG.md1
-rw-r--r--config/config.exs3
-rw-r--r--docs/api/pleroma_api.md9
-rw-r--r--lib/pleroma/activity.ex16
-rw-r--r--lib/pleroma/notification.ex7
-rw-r--r--lib/pleroma/user.ex71
-rw-r--r--lib/pleroma/user/query.ex6
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex1
-rw-r--r--lib/pleroma/web/router.ex1
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex11
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex9
-rw-r--r--priv/repo/migrations/20190411094120_add_index_on_user_info_deactivated.exs7
-rw-r--r--test/user_test.exs79
-rw-r--r--test/web/twitter_api/util_controller_test.exs18
14 files changed, 202 insertions, 37 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c0baa317..17e913648 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -64,6 +64,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Deps: Updated Ecto to 3.0.7
- Don't ship finmoji by default, they can be installed as an emoji pack
- Admin API: Move the user related API to `api/pleroma/admin/users`
+- Hide deactivated users and their statuses
### Fixed
- Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended.
diff --git a/config/config.exs b/config/config.exs
index 45034a775..8d44c96de 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -424,7 +424,8 @@ config :pleroma_job_queue, :queues,
mailer: 10,
transmogrifier: 20,
scheduled_activities: 10,
- background: 5
+ background: 5,
+ user: 10
config :pleroma, :fetch_initial_posts,
enabled: false,
diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md
index 190846de9..dd0b6ca73 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 c121e800f..4a0919478 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -132,7 +132,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
@@ -200,6 +203,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
@@ -314,4 +318,14 @@ defmodule Pleroma.Activity do
def query_by_actor(actor) do
from(a in Activity, where: a.actor == ^actor)
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 dd274cf6b..844264307 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 474de9ba5..3eb684c3a 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -105,10 +105,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,
@@ -117,6 +115,20 @@ defmodule Pleroma.User do
}
end
+ def 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{} = user) do
+ user
+ |> get_friends_query()
+ |> Repo.aggregate(:count, :id)
+ end
+
def remote_user_creation(params) do
params =
params
@@ -255,7 +267,7 @@ defmodule Pleroma.User do
candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
autofollowed_users =
- User.Query.build(%{nickname: candidates, local: true})
+ User.Query.build(%{nickname: candidates, local: true, deactivated: false})
|> Repo.all()
follow_all(user, autofollowed_users)
@@ -576,7 +588,7 @@ defmodule Pleroma.User do
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
def get_followers_query(%User{} = user, nil) do
- User.Query.build(%{followers: user})
+ User.Query.build(%{followers: user, deactivated: false})
end
def get_followers_query(user, page) do
@@ -601,7 +613,7 @@ defmodule Pleroma.User do
@spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
def get_friends_query(%User{} = user, nil) do
- User.Query.build(%{friends: user})
+ User.Query.build(%{friends: user, deactivated: false})
end
def get_friends_query(user, page) do
@@ -691,16 +703,16 @@ 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
follower_count_query =
- User.Query.build(%{followers: user}) |> select([u], %{count: count(u.id)})
+ User.Query.build(%{followers: user, deactivated: false})
+ |> select([u], %{count: count(u.id)})
User
|> where(id: ^user.id)
@@ -725,7 +737,7 @@ defmodule Pleroma.User do
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
def get_users_from_set(ap_ids, local_only \\ true) do
- criteria = %{ap_id: ap_ids}
+ criteria = %{ap_id: ap_ids, deactivated: false}
criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
User.Query.build(criteria)
@@ -734,7 +746,7 @@ defmodule Pleroma.User do
@spec get_recipients_from_activity(Activity.t()) :: [User.t()]
def get_recipients_from_activity(%Activity{recipients: to}) do
- User.Query.build(%{recipients_from_activity: to, local: true})
+ User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
|> Repo.all()
end
@@ -832,6 +844,7 @@ defmodule Pleroma.User do
^processed_query
)
)
+ |> restrict_deactivated()
end
defp trigram_search_subquery(term) do
@@ -850,6 +863,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
@@ -999,19 +1013,19 @@ defmodule Pleroma.User do
@spec muted_users(User.t()) :: [User.t()]
def muted_users(user) do
- User.Query.build(%{ap_id: user.info.mutes})
+ User.Query.build(%{ap_id: user.info.mutes, deactivated: false})
|> Repo.all()
end
@spec blocked_users(User.t()) :: [User.t()]
def blocked_users(user) do
- User.Query.build(%{ap_id: user.info.blocks})
+ User.Query.build(%{ap_id: user.info.blocks, deactivated: false})
|> Repo.all()
end
@spec subscribers(User.t()) :: [User.t()]
def subscribers(user) do
- User.Query.build(%{ap_id: user.info.subscribers})
+ User.Query.build(%{ap_id: user.info.subscribers, deactivated: false})
|> Repo.all()
end
@@ -1039,14 +1053,27 @@ defmodule Pleroma.User do
update_and_set_cache(cng)
end
+ def deactivate_async(user, status \\ true) do
+ PleromaJobQueue.enqueue(:background, __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
@@ -1320,7 +1347,7 @@ defmodule Pleroma.User do
@spec all_superusers() :: [User.t()]
def all_superusers do
- User.Query.build(%{super_users: true, local: true})
+ User.Query.build(%{super_users: true, local: true, deactivated: false})
|> Repo.all()
end
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index 2dfe5ce92..ace9c05f2 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -118,7 +118,11 @@ defmodule Pleroma.User.Query do
|> where([u], not is_nil(u.nickname))
end
- defp compose_query({:deactivated, _}, query) do
+ defp compose_query({:deactivated, false}, query) do
+ User.restrict_deactivated(query)
+ end
+
+ defp compose_query({:deactivated, true}, query) do
where(query, [u], fragment("?->'deactivated' @> 'true'", u.info))
|> where([u], not is_nil(u.nickname))
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 11777c220..9a137d8de 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -854,6 +854,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 51146d010..80af0afe1 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -215,6 +215,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 c03f8ab3a..7b7fd912b 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 1362ef57c..41e1c2877 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -236,12 +236,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 60de0206e..0b65e89e9 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,73 @@ 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}"})
+
+ activity = Repo.preload(activity, :bookmark)
+
+ [notification] = Pleroma.Notification.for_user(user2)
+ assert notification.activity.id == activity.id
+
+ assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark)
+
+ 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 56474447b..14a8225f0 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -251,4 +251,22 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
assert conn.status in [200, 503]
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