Merge branch 'feature/bulk-confirmation' into 'develop'
Bulk account confirmation actions Closes #2085 See merge request pleroma/pleroma!2975
This commit is contained in:
commit
1672d8b37c
|
@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`)
|
||||||
|
- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Search: Users are now findable by their urls.
|
- Search: Users are now findable by their urls.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Managing emails
|
# EMail administration tasks
|
||||||
|
|
||||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||||
|
|
||||||
|
@ -30,3 +30,17 @@ Example:
|
||||||
```sh
|
```sh
|
||||||
mix pleroma.email test --to root@example.org
|
mix pleroma.email test --to root@example.org
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Send confirmation emails to all unconfirmed user accounts
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl email send_confirmation_mails
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mix pleroma.email send_confirmation_mails
|
||||||
|
```
|
||||||
|
|
|
@ -224,9 +224,10 @@
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
- `--admin`/`--no-admin` - whether the user should be an admin
|
||||||
|
- `--confirmed`/`--no-confirmed` - whether the user account is confirmed
|
||||||
- `--locked`/`--no-locked` - whether the user should be locked
|
- `--locked`/`--no-locked` - whether the user should be locked
|
||||||
- `--moderator`/`--no-moderator` - whether the user should be a moderator
|
- `--moderator`/`--no-moderator` - whether the user should be a moderator
|
||||||
- `--admin`/`--no-admin` - whether the user should be an admin
|
|
||||||
|
|
||||||
## Add tags to a user
|
## Add tags to a user
|
||||||
|
|
||||||
|
@ -271,3 +272,33 @@
|
||||||
```sh
|
```sh
|
||||||
mix pleroma.user toggle_confirmed <nickname>
|
mix pleroma.user toggle_confirmed <nickname>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Set confirmation status for all regular active users
|
||||||
|
*Admins and moderators are excluded*
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl user confirm_all
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mix pleroma.user confirm_all
|
||||||
|
```
|
||||||
|
|
||||||
|
## Revoke confirmation status for all regular active users
|
||||||
|
*Admins and moderators are excluded*
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl user unconfirm_all
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mix pleroma.user unconfirm_all
|
||||||
|
```
|
||||||
|
|
|
@ -2,11 +2,11 @@ defmodule Mix.Tasks.Pleroma.Email do
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
import Mix.Pleroma
|
import Mix.Pleroma
|
||||||
|
|
||||||
@shortdoc "Simple Email test"
|
@shortdoc "Email administrative tasks"
|
||||||
@moduledoc File.read!("docs/administration/CLI_tasks/email.md")
|
@moduledoc File.read!("docs/administration/CLI_tasks/email.md")
|
||||||
|
|
||||||
def run(["test" | args]) do
|
def run(["test" | args]) do
|
||||||
Mix.Pleroma.start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
{options, [], []} =
|
{options, [], []} =
|
||||||
OptionParser.parse(
|
OptionParser.parse(
|
||||||
|
@ -21,4 +21,20 @@ def run(["test" | args]) do
|
||||||
|
|
||||||
shell_info("Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}")
|
shell_info("Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run(["resend_confirmation_emails"]) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
shell_info("Sending emails to all unconfirmed users")
|
||||||
|
|
||||||
|
Pleroma.User.Query.build(%{
|
||||||
|
local: true,
|
||||||
|
deactivated: false,
|
||||||
|
confirmation_pending: true,
|
||||||
|
invisible: false
|
||||||
|
})
|
||||||
|
|> Pleroma.Repo.chunk_stream(500)
|
||||||
|
|> Stream.each(&Pleroma.User.try_send_confirmation_email(&1))
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -196,17 +196,24 @@ def run(["set", nickname | rest]) do
|
||||||
OptionParser.parse(
|
OptionParser.parse(
|
||||||
rest,
|
rest,
|
||||||
strict: [
|
strict: [
|
||||||
moderator: :boolean,
|
|
||||||
admin: :boolean,
|
admin: :boolean,
|
||||||
locked: :boolean
|
confirmed: :boolean,
|
||||||
|
locked: :boolean,
|
||||||
|
moderator: :boolean
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
user =
|
user =
|
||||||
case Keyword.get(options, :moderator) do
|
case Keyword.get(options, :admin) do
|
||||||
nil -> user
|
nil -> user
|
||||||
value -> set_moderator(user, value)
|
value -> set_admin(user, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
user =
|
||||||
|
case Keyword.get(options, :confirmed) do
|
||||||
|
nil -> user
|
||||||
|
value -> set_confirmed(user, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
user =
|
user =
|
||||||
|
@ -216,9 +223,9 @@ def run(["set", nickname | rest]) do
|
||||||
end
|
end
|
||||||
|
|
||||||
_user =
|
_user =
|
||||||
case Keyword.get(options, :admin) do
|
case Keyword.get(options, :moderator) do
|
||||||
nil -> user
|
nil -> user
|
||||||
value -> set_admin(user, value)
|
value -> set_moderator(user, value)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -353,6 +360,42 @@ def run(["toggle_confirmed", nickname]) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run(["confirm_all"]) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
Pleroma.User.Query.build(%{
|
||||||
|
local: true,
|
||||||
|
deactivated: false,
|
||||||
|
is_moderator: false,
|
||||||
|
is_admin: false,
|
||||||
|
invisible: false
|
||||||
|
})
|
||||||
|
|> Pleroma.Repo.chunk_stream(500, :batches)
|
||||||
|
|> Stream.each(fn users ->
|
||||||
|
users
|
||||||
|
|> Enum.each(fn user -> User.need_confirmation(user, false) end)
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(["unconfirm_all"]) do
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
Pleroma.User.Query.build(%{
|
||||||
|
local: true,
|
||||||
|
deactivated: false,
|
||||||
|
is_moderator: false,
|
||||||
|
is_admin: false,
|
||||||
|
invisible: false
|
||||||
|
})
|
||||||
|
|> Pleroma.Repo.chunk_stream(500, :batches)
|
||||||
|
|> Stream.each(fn users ->
|
||||||
|
users
|
||||||
|
|> Enum.each(fn user -> User.need_confirmation(user, true) end)
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
|
|
||||||
def run(["sign_out", nickname]) do
|
def run(["sign_out", nickname]) do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
|
@ -410,4 +453,11 @@ defp set_locked(user, value) do
|
||||||
shell_info("Locked status of #{user.nickname}: #{user.locked}")
|
shell_info("Locked status of #{user.nickname}: #{user.locked}")
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp set_confirmed(user, value) do
|
||||||
|
{:ok, user} = User.need_confirmation(user, !value)
|
||||||
|
|
||||||
|
shell_info("Confirmation pending status of #{user.nickname}: #{user.confirmation_pending}")
|
||||||
|
user
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -813,7 +813,8 @@ def send_welcome_email(%User{email: email} = user) when is_binary(email) do
|
||||||
def send_welcome_email(_), do: {:ok, :noop}
|
def send_welcome_email(_), do: {:ok, :noop}
|
||||||
|
|
||||||
@spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
|
@spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
|
||||||
def try_send_confirmation_email(%User{confirmation_pending: true} = user) do
|
def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user)
|
||||||
|
when is_binary(email) do
|
||||||
if Config.get([:instance, :account_activation_required]) do
|
if Config.get([:instance, :account_activation_required]) do
|
||||||
send_confirmation_email(user)
|
send_confirmation_email(user)
|
||||||
{:ok, :enqueued}
|
{:ok, :enqueued}
|
||||||
|
@ -2071,6 +2072,13 @@ def toggle_confirmation(users) do
|
||||||
Enum.map(users, &toggle_confirmation/1)
|
Enum.map(users, &toggle_confirmation/1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
|
||||||
|
def need_confirmation(%User{} = user, bool) do
|
||||||
|
user
|
||||||
|
|> confirmation_changeset(need_confirmation: bool)
|
||||||
|
|> update_and_set_cache()
|
||||||
|
end
|
||||||
|
|
||||||
def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
|
def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
|
||||||
mascot
|
mascot
|
||||||
end
|
end
|
||||||
|
|
|
@ -110,12 +110,12 @@ defp compose_query({:tags, tags}, query) when is_list(tags) and length(tags) > 0
|
||||||
where(query, [u], fragment("? && ?", u.tags, ^tags))
|
where(query, [u], fragment("? && ?", u.tags, ^tags))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compose_query({:is_admin, _}, query) do
|
defp compose_query({:is_admin, bool}, query) do
|
||||||
where(query, [u], u.is_admin)
|
where(query, [u], u.is_admin == ^bool)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compose_query({:is_moderator, _}, query) do
|
defp compose_query({:is_moderator, bool}, query) do
|
||||||
where(query, [u], u.is_moderator)
|
where(query, [u], u.is_moderator == ^bool)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compose_query({:super_users, _}, query) do
|
defp compose_query({:super_users, _}, query) do
|
||||||
|
@ -148,6 +148,10 @@ defp compose_query({:deactivated, true}, query) do
|
||||||
where(query, [u], u.deactivated == ^true)
|
where(query, [u], u.deactivated == ^true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp compose_query({:confirmation_pending, bool}, query) do
|
||||||
|
where(query, [u], u.confirmation_pending == ^bool)
|
||||||
|
end
|
||||||
|
|
||||||
defp compose_query({:need_approval, _}, query) do
|
defp compose_query({:need_approval, _}, query) do
|
||||||
where(query, [u], u.approval_pending)
|
where(query, [u], u.approval_pending)
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,8 @@ defmodule Mix.Tasks.Pleroma.EmailTest do
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Tests.ObanHelpers
|
alias Pleroma.Tests.ObanHelpers
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
Mix.shell(Mix.Shell.Process)
|
Mix.shell(Mix.Shell.Process)
|
||||||
|
|
||||||
|
@ -17,6 +19,7 @@ defmodule Mix.Tasks.Pleroma.EmailTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
setup do: clear_config([Pleroma.Emails.Mailer, :enabled], true)
|
setup do: clear_config([Pleroma.Emails.Mailer, :enabled], true)
|
||||||
|
setup do: clear_config([:instance, :account_activation_required], true)
|
||||||
|
|
||||||
describe "pleroma.email test" do
|
describe "pleroma.email test" do
|
||||||
test "Sends test email with no given address" do
|
test "Sends test email with no given address" do
|
||||||
|
@ -50,5 +53,71 @@ test "Sends test email with given address" do
|
||||||
html_body: ~r/a test email was requested./i
|
html_body: ~r/a test email was requested./i
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "Sends confirmation emails" do
|
||||||
|
local_user1 =
|
||||||
|
insert(:user, %{
|
||||||
|
confirmation_pending: true,
|
||||||
|
confirmation_token: "mytoken",
|
||||||
|
deactivated: false,
|
||||||
|
email: "local1@pleroma.com",
|
||||||
|
local: true
|
||||||
|
})
|
||||||
|
|
||||||
|
local_user2 =
|
||||||
|
insert(:user, %{
|
||||||
|
confirmation_pending: true,
|
||||||
|
confirmation_token: "mytoken",
|
||||||
|
deactivated: false,
|
||||||
|
email: "local2@pleroma.com",
|
||||||
|
local: true
|
||||||
|
})
|
||||||
|
|
||||||
|
:ok = Mix.Tasks.Pleroma.Email.run(["resend_confirmation_emails"])
|
||||||
|
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
assert_email_sent(to: {local_user1.name, local_user1.email})
|
||||||
|
assert_email_sent(to: {local_user2.name, local_user2.email})
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Does not send confirmation email to inappropriate users" do
|
||||||
|
# confirmed user
|
||||||
|
insert(:user, %{
|
||||||
|
confirmation_pending: false,
|
||||||
|
confirmation_token: "mytoken",
|
||||||
|
deactivated: false,
|
||||||
|
email: "confirmed@pleroma.com",
|
||||||
|
local: true
|
||||||
|
})
|
||||||
|
|
||||||
|
# remote user
|
||||||
|
insert(:user, %{
|
||||||
|
deactivated: false,
|
||||||
|
email: "remote@not-pleroma.com",
|
||||||
|
local: false
|
||||||
|
})
|
||||||
|
|
||||||
|
# deactivated user =
|
||||||
|
insert(:user, %{
|
||||||
|
deactivated: true,
|
||||||
|
email: "deactivated@pleroma.com",
|
||||||
|
local: false
|
||||||
|
})
|
||||||
|
|
||||||
|
# invisible user
|
||||||
|
insert(:user, %{
|
||||||
|
deactivated: false,
|
||||||
|
email: "invisible@pleroma.com",
|
||||||
|
local: true,
|
||||||
|
invisible: true
|
||||||
|
})
|
||||||
|
|
||||||
|
:ok = Mix.Tasks.Pleroma.Email.run(["resend_confirmation_emails"])
|
||||||
|
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
refute_email_sent()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -225,47 +225,64 @@ test "no user to deactivate" do
|
||||||
test "All statuses set" do
|
test "All statuses set" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
Mix.Tasks.Pleroma.User.run(["set", user.nickname, "--moderator", "--admin", "--locked"])
|
Mix.Tasks.Pleroma.User.run([
|
||||||
|
"set",
|
||||||
|
user.nickname,
|
||||||
|
"--admin",
|
||||||
|
"--confirmed",
|
||||||
|
"--locked",
|
||||||
|
"--moderator"
|
||||||
|
])
|
||||||
|
|
||||||
assert_received {:mix_shell, :info, [message]}
|
assert_received {:mix_shell, :info, [message]}
|
||||||
assert message =~ ~r/Moderator status .* true/
|
assert message =~ ~r/Admin status .* true/
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :info, [message]}
|
||||||
|
assert message =~ ~r/Confirmation pending .* false/
|
||||||
|
|
||||||
assert_received {:mix_shell, :info, [message]}
|
assert_received {:mix_shell, :info, [message]}
|
||||||
assert message =~ ~r/Locked status .* true/
|
assert message =~ ~r/Locked status .* true/
|
||||||
|
|
||||||
assert_received {:mix_shell, :info, [message]}
|
assert_received {:mix_shell, :info, [message]}
|
||||||
assert message =~ ~r/Admin status .* true/
|
assert message =~ ~r/Moderator status .* true/
|
||||||
|
|
||||||
user = User.get_cached_by_nickname(user.nickname)
|
user = User.get_cached_by_nickname(user.nickname)
|
||||||
assert user.is_moderator
|
assert user.is_moderator
|
||||||
assert user.locked
|
assert user.locked
|
||||||
assert user.is_admin
|
assert user.is_admin
|
||||||
|
refute user.confirmation_pending
|
||||||
end
|
end
|
||||||
|
|
||||||
test "All statuses unset" do
|
test "All statuses unset" do
|
||||||
user = insert(:user, locked: true, is_moderator: true, is_admin: true)
|
user =
|
||||||
|
insert(:user, locked: true, is_moderator: true, is_admin: true, confirmation_pending: true)
|
||||||
|
|
||||||
Mix.Tasks.Pleroma.User.run([
|
Mix.Tasks.Pleroma.User.run([
|
||||||
"set",
|
"set",
|
||||||
user.nickname,
|
user.nickname,
|
||||||
"--no-moderator",
|
|
||||||
"--no-admin",
|
"--no-admin",
|
||||||
"--no-locked"
|
"--no-confirmed",
|
||||||
|
"--no-locked",
|
||||||
|
"--no-moderator"
|
||||||
])
|
])
|
||||||
|
|
||||||
assert_received {:mix_shell, :info, [message]}
|
assert_received {:mix_shell, :info, [message]}
|
||||||
assert message =~ ~r/Moderator status .* false/
|
assert message =~ ~r/Admin status .* false/
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :info, [message]}
|
||||||
|
assert message =~ ~r/Confirmation pending .* true/
|
||||||
|
|
||||||
assert_received {:mix_shell, :info, [message]}
|
assert_received {:mix_shell, :info, [message]}
|
||||||
assert message =~ ~r/Locked status .* false/
|
assert message =~ ~r/Locked status .* false/
|
||||||
|
|
||||||
assert_received {:mix_shell, :info, [message]}
|
assert_received {:mix_shell, :info, [message]}
|
||||||
assert message =~ ~r/Admin status .* false/
|
assert message =~ ~r/Moderator status .* false/
|
||||||
|
|
||||||
user = User.get_cached_by_nickname(user.nickname)
|
user = User.get_cached_by_nickname(user.nickname)
|
||||||
refute user.is_moderator
|
refute user.is_moderator
|
||||||
refute user.locked
|
refute user.locked
|
||||||
refute user.is_admin
|
refute user.is_admin
|
||||||
|
assert user.confirmation_pending
|
||||||
end
|
end
|
||||||
|
|
||||||
test "no user to set status" do
|
test "no user to set status" do
|
||||||
|
@ -554,4 +571,44 @@ test "it prints an error message when user is not exist" do
|
||||||
assert message =~ "Could not change user tags"
|
assert message =~ "Could not change user tags"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "bulk confirm and unconfirm" do
|
||||||
|
test "confirm all" do
|
||||||
|
user1 = insert(:user, confirmation_pending: true)
|
||||||
|
user2 = insert(:user, confirmation_pending: true)
|
||||||
|
|
||||||
|
assert user1.confirmation_pending
|
||||||
|
assert user2.confirmation_pending
|
||||||
|
|
||||||
|
Mix.Tasks.Pleroma.User.run(["confirm_all"])
|
||||||
|
|
||||||
|
user1 = User.get_cached_by_nickname(user1.nickname)
|
||||||
|
user2 = User.get_cached_by_nickname(user2.nickname)
|
||||||
|
|
||||||
|
refute user1.confirmation_pending
|
||||||
|
refute user2.confirmation_pending
|
||||||
|
end
|
||||||
|
|
||||||
|
test "unconfirm all" do
|
||||||
|
user1 = insert(:user, confirmation_pending: false)
|
||||||
|
user2 = insert(:user, confirmation_pending: false)
|
||||||
|
admin = insert(:user, is_admin: true, confirmation_pending: false)
|
||||||
|
mod = insert(:user, is_moderator: true, confirmation_pending: false)
|
||||||
|
|
||||||
|
refute user1.confirmation_pending
|
||||||
|
refute user2.confirmation_pending
|
||||||
|
|
||||||
|
Mix.Tasks.Pleroma.User.run(["unconfirm_all"])
|
||||||
|
|
||||||
|
user1 = User.get_cached_by_nickname(user1.nickname)
|
||||||
|
user2 = User.get_cached_by_nickname(user2.nickname)
|
||||||
|
admin = User.get_cached_by_nickname(admin.nickname)
|
||||||
|
mod = User.get_cached_by_nickname(mod.nickname)
|
||||||
|
|
||||||
|
assert user1.confirmation_pending
|
||||||
|
assert user2.confirmation_pending
|
||||||
|
refute admin.confirmation_pending
|
||||||
|
refute mod.confirmation_pending
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue