allow users to disable their own account

This commit is contained in:
Egor Kislitsyn 2019-03-04 19:55:11 +07:00
parent dd5865535e
commit eb84de0143
16 changed files with 167 additions and 51 deletions

View file

@ -343,7 +343,8 @@
config :pleroma, Pleroma.Jobs, config :pleroma, Pleroma.Jobs,
federator_incoming: [max_jobs: 50], federator_incoming: [max_jobs: 50],
federator_outgoing: [max_jobs: 50], federator_outgoing: [max_jobs: 50],
mailer: [max_jobs: 10] mailer: [max_jobs: 10],
user: [max_jobs: 10]
config :auto_linker, config :auto_linker,
opts: [ opts: [

View file

@ -23,7 +23,7 @@ defmodule Mix.Tasks.Pleroma.User do
- `--password PASSWORD` - the user's password - `--password PASSWORD` - the user's password
- `--moderator`/`--no-moderator` - whether the user is a moderator - `--moderator`/`--no-moderator` - whether the user is a moderator
- `--admin`/`--no-admin` - whether the user is an admin - `--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. ## Generate an invite link.
@ -37,6 +37,10 @@ defmodule Mix.Tasks.Pleroma.User do
mix pleroma.user toggle_activated NICKNAME 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 ## Unsubscribe local users from user's account and deactivate it
mix pleroma.user unsubscribe NICKNAME mix pleroma.user unsubscribe NICKNAME
@ -170,6 +174,20 @@ def run(["toggle_activated", nickname]) do
end end
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 def run(["reset_password", nickname]) do
Common.start_pleroma() Common.start_pleroma()

View file

@ -42,7 +42,10 @@ def get_by_ap_id(ap_id) do
end end
def get_by_id(id) do def get_by_id(id) do
Repo.get(Activity, id) Activity
|> where([a], a.id == ^id)
|> restrict_disabled_users()
|> Repo.one()
end end
def by_object_ap_id(ap_id) do def by_object_ap_id(ap_id) do
@ -92,6 +95,7 @@ def get_all_create_by_object_ap_id(ap_id) do
def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
create_by_object_ap_id(ap_id) create_by_object_ap_id(ap_id)
|> restrict_disabled_users()
|> Repo.one() |> Repo.one()
end end
@ -123,4 +127,14 @@ def all_by_actor_and_id(actor, status_ids) do
|> where([s], s.actor == ^actor) |> where([s], s.actor == ^actor)
|> Repo.all() |> Repo.all()
end 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 end

View file

@ -41,7 +41,6 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.User alias Pleroma.User
alias Pleroma.Repo
def start_link(ref, socket, transport, opts) do def start_link(ref, socket, transport, opts) do
pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts]) pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
@ -110,7 +109,7 @@ def response("/main/all") do
end end
def response("/notices/" <> id) do 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 true <- Visibility.is_public?(activity) do
activities = activities =
ActivityPub.fetch_activities_for_context(activity.data["context"]) ActivityPub.fetch_activities_for_context(activity.data["context"])

View file

@ -36,22 +36,22 @@ defp restrict_since(query, %{"since_id" => since_id}) do
defp restrict_since(query, _), do: query defp restrict_since(query, _), do: query
def for_user(user, opts \\ %{}) do def for_user(user, opts \\ %{}) do
query = from(
from( n in Notification,
n in Notification, where: n.user_id == ^user.id,
where: n.user_id == ^user.id, order_by: [desc: n.id],
order_by: [desc: n.id], join: activity in assoc(n, :activity),
join: activity in assoc(n, :activity), preload: [activity: activity],
preload: [activity: activity], limit: 20,
limit: 20 where:
) fragment(
"? not in (SELECT ap_id FROM users WHERE info->'disabled' @> 'true')",
query = activity.actor
query )
|> restrict_since(opts) )
|> restrict_max(opts) |> restrict_since(opts)
|> restrict_max(opts)
Repo.all(query) |> Repo.all()
end end
def set_read_up_to(%{id: user_id} = _user, id) do def set_read_up_to(%{id: user_id} = _user, id) do

View file

@ -108,10 +108,8 @@ def ap_followers(%User{} = user) do
end end
def user_info(%User{} = user) do 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, note_count: user.info.note_count,
follower_count: user.info.follower_count, follower_count: user.info.follower_count,
locked: user.info.locked, locked: user.info.locked,
@ -120,6 +118,23 @@ def user_info(%User{} = user) do
} }
end 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 def remote_user_creation(params) do
params = params =
params params
@ -545,6 +560,7 @@ def get_followers_query(%User{id: id, follower_address: follower_address}, nil)
where: fragment("? <@ ?", ^[follower_address], u.following), where: fragment("? <@ ?", ^[follower_address], u.following),
where: u.id != ^id where: u.id != ^id
) )
|> restrict_disabled()
end end
def get_followers_query(user, page) do def get_followers_query(user, page) do
@ -572,6 +588,7 @@ def get_friends_query(%User{id: id, following: following}, nil) do
where: u.follower_address in ^following, where: u.follower_address in ^following,
where: u.id != ^id where: u.id != ^id
) )
|> restrict_disabled()
end end
def get_friends_query(user, page) do def get_friends_query(user, page) do
@ -681,11 +698,10 @@ def update_note_count(%User{} = user) do
info_cng = User.Info.set_note_count(user.info, note_count) info_cng = User.Info.set_note_count(user.info, note_count)
cng = user
change(user) |> change()
|> put_embed(:info, info_cng) |> put_embed(:info, info_cng)
|> update_and_set_cache()
update_and_set_cache(cng)
end end
def update_follower_count(%User{} = user) do def update_follower_count(%User{} = user) do
@ -694,6 +710,7 @@ def update_follower_count(%User{} = user) do
|> where([u], ^user.follower_address in u.following) |> where([u], ^user.follower_address in u.following)
|> where([u], u.id != ^user.id) |> where([u], u.id != ^user.id)
|> select([u], %{count: count(u.id)}) |> select([u], %{count: count(u.id)})
|> restrict_disabled()
User User
|> where(id: ^user.id) |> where(id: ^user.id)
@ -860,6 +877,7 @@ defp fts_search_subquery(term, query \\ User) do
^processed_query ^processed_query
) )
) )
|> restrict_disabled()
end end
defp trigram_search_subquery(term) do defp trigram_search_subquery(term) do
@ -876,6 +894,7 @@ defp trigram_search_subquery(term) do
}, },
where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term) where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
) )
|> restrict_disabled()
end end
defp boost_search_results(results, nil), do: results defp boost_search_results(results, nil), do: results
@ -1062,11 +1081,10 @@ def moderator_user_query do
def deactivate(%User{} = user, status \\ true) do def deactivate(%User{} = user, status \\ true) do
info_cng = User.Info.set_activation_status(user.info, status) info_cng = User.Info.set_activation_status(user.info, status)
cng = user
change(user) |> change()
|> put_embed(:info, info_cng) |> put_embed(:info, info_cng)
|> update_and_set_cache()
update_and_set_cache(cng)
end end
def delete(%User{} = user) do def delete(%User{} = user) do
@ -1100,6 +1118,26 @@ def delete(%User{} = user) do
{:ok, user} {:ok, user}
end 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 def html_filter_policy(%User{info: %{no_rich_text: true}}) do
Pleroma.HTML.Scrubber.TwitterText Pleroma.HTML.Scrubber.TwitterText
end end

View file

@ -36,6 +36,7 @@ defmodule Pleroma.User.Info do
field(:hide_follows, :boolean, default: false) field(:hide_follows, :boolean, default: false)
field(:pinned_activities, {:array, :string}, default: []) field(:pinned_activities, {:array, :string}, default: [])
field(:flavour, :string, default: nil) field(:flavour, :string, default: nil)
field(:disabled, :boolean, default: false)
# Found in the wild # Found in the wild
# ap_id -> Where is this used? # ap_id -> Where is this used?
@ -54,6 +55,14 @@ def set_activation_status(info, deactivated) do
|> validate_required([:deactivated]) |> validate_required([:deactivated])
end 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 def add_to_note_count(info, number) do
set_note_count(info, info.note_count + number) set_note_count(info, info.note_count + number)
end end

View file

@ -703,6 +703,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_replies(opts) |> restrict_replies(opts)
|> restrict_reblogs(opts) |> restrict_reblogs(opts)
|> restrict_pinned(opts) |> restrict_pinned(opts)
|> Activity.restrict_disabled_users()
end end
def fetch_activities(recipients, opts \\ %{}) do def fetch_activities(recipients, opts \\ %{}) do

View file

@ -44,6 +44,16 @@ def user_create(
|> json(user.nickname) |> json(user.nickname)
end 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 def user_toggle_activation(conn, %{"nickname" => nickname}) do
user = User.get_by_nickname(nickname) user = User.get_by_nickname(nickname)

View file

@ -17,7 +17,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
# This is a hack for twidere. # This is a hack for twidere.
def get_by_id_or_ap_id(id) do 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 && activity &&
if activity.data["type"] == "Create" do if activity.data["type"] == "Create" do
@ -30,7 +30,7 @@ def get_by_id_or_ap_id(id) do
def get_replied_to_activity(""), do: nil def get_replied_to_activity(""), do: nil
def get_replied_to_activity(id) when not is_nil(id) do def get_replied_to_activity(id) when not is_nil(id) do
Repo.get(Activity, id) Activity.get_by_id(id)
end end
def get_replied_to_activity(_), do: nil def get_replied_to_activity(_), do: nil

View file

@ -307,7 +307,7 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do
end end
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do 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 true <- Visibility.visible_for_user?(activity, user) do
conn conn
|> put_view(StatusView) |> put_view(StatusView)
@ -316,7 +316,7 @@ def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do 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 <- activities <-
ActivityPub.fetch_activities_for_context(activity.data["context"], %{ ActivityPub.fetch_activities_for_context(activity.data["context"], %{
"blocking_user" => user, "blocking_user" => user,
@ -448,7 +448,7 @@ def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
end end
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do 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), %User{} = user <- User.get_by_nickname(user.nickname),
true <- Visibility.visible_for_user?(activity, user), true <- Visibility.visible_for_user?(activity, user),
{:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do {:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do
@ -459,7 +459,7 @@ def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do 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), %User{} = user <- User.get_by_nickname(user.nickname),
true <- Visibility.visible_for_user?(activity, user), true <- Visibility.visible_for_user?(activity, user),
{:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do {:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do
@ -583,7 +583,7 @@ def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
end end
def favourited_by(conn, %{"id" => id}) do 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) q = from(u in User, where: u.ap_id in ^likes)
users = Repo.all(q) users = Repo.all(q)
@ -596,7 +596,7 @@ def favourited_by(conn, %{"id" => id}) do
end end
def reblogged_by(conn, %{"id" => id}) do 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) q = from(u in User, where: u.ap_id in ^announces)
users = Repo.all(q) users = Repo.all(q)

View file

@ -143,6 +143,7 @@ defmodule Pleroma.Web.Router do
get("/users/search", AdminAPIController, :search_users) get("/users/search", AdminAPIController, :search_users)
delete("/user", AdminAPIController, :user_delete) delete("/user", AdminAPIController, :user_delete)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation) patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
patch("/users/:nickname/toggle_disabled", AdminAPIController, :user_toggle_disabled)
post("/user", AdminAPIController, :user_create) post("/user", AdminAPIController, :user_create)
put("/users/tag", AdminAPIController, :tag_users) put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users) delete("/users/tag", AdminAPIController, :untag_users)
@ -183,6 +184,7 @@ defmodule Pleroma.Web.Router do
post("/change_password", UtilController, :change_password) post("/change_password", UtilController, :change_password)
post("/delete_account", UtilController, :delete_account) post("/delete_account", UtilController, :delete_account)
post("/disable_account", UtilController, :disable_account)
end end
scope [] do scope [] do

View file

@ -311,6 +311,17 @@ def delete_account(%{assigns: %{user: user}} = conn, params) do
end end
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 def captcha(conn, _params) do
json(conn, Pleroma.Captcha.new()) json(conn, Pleroma.Captcha.new())
end end

View file

@ -21,7 +21,7 @@ def create_status(%User{} = user, %{"status" => _} = data) do
end end
def delete(%User{} = user, id) do 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} <- CommonAPI.delete(id, user) do
{:ok, activity} {:ok, activity}
end end
@ -232,21 +232,27 @@ def password_reset(nickname_or_email) do
def get_user(user \\ nil, params) do def get_user(user \\ nil, params) do
case params do case params do
%{"user_id" => user_id} -> %{"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 -> nil ->
{:error, "No user with such user_id"} {:error, "No user with such user_id"}
_ -> %User{info: %{disabled: true}} ->
{:ok, target} {:error, "User has been disabled"}
user ->
{:ok, user}
end end
%{"screen_name" => nickname} -> %{"screen_name" => nickname} ->
case target = Repo.get_by(User, nickname: nickname) do case User.get_by_nickname(nickname) do
nil -> nil ->
{:error, "No user with such screen_name"} {:error, "No user with such screen_name"}
_ -> %User{info: %{disabled: true}} ->
{:ok, target} {:error, "User has been disabled"}
user ->
{:ok, user}
end end
_ -> _ ->

View file

@ -269,7 +269,7 @@ def unfollow(%{assigns: %{user: user}} = conn, params) do
end end
def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do 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 true <- Visibility.visible_for_user?(activity, user) do
conn conn
|> put_view(ActivityView) |> put_view(ActivityView)
@ -341,7 +341,7 @@ def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do
end end
def get_by_id_or_ap_id(id) do 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 if activity.data["type"] == "Create" do
activity activity

View file

@ -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