Merge branch 'develop' into feature/bulk-confirmation
authorMark Felder <feld@FreeBSD.org>
Wed, 23 Sep 2020 16:56:22 +0000 (11:56 -0500)
committerMark Felder <feld@FreeBSD.org>
Wed, 23 Sep 2020 16:56:22 +0000 (11:56 -0500)
CHANGELOG.md
docs/administration/CLI_tasks/email.md
docs/administration/CLI_tasks/user.md
lib/mix/tasks/pleroma/email.ex
lib/mix/tasks/pleroma/user.ex
lib/pleroma/user.ex
lib/pleroma/user/query.ex
test/tasks/user_test.exs

index 5f5d01af34e94be5b8781931aa0d42951db004b6..4e9aae73614b172d2fb755fbc2fc36047af685af 100644 (file)
@@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## 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
 
 - Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
index 00d2e74f83d9374b242cf406e8f0b0ea3110eff8..d9aa0e71ba1b433cebb716f51d8ec379916af3f8 100644 (file)
@@ -1,4 +1,4 @@
-# Managing emails
+# EMail administration tasks
 
 {! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
@@ -30,3 +30,17 @@ Example:
     ```sh
     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
+    ```
index 3e7f028ba1db471498eae190b0c38fab2196f74d..c64ed4f223b8cd5278f22d17245c58a3bbd32d43 100644 (file)
     ```
 
 ### 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
 - `--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
 
     ```sh
     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
+    ```
index d3fac6ec8f3420da3ec0b765932b49ba432f42e6..c0bef03861c96d2a4e596764af13af9eea6a18cd 100644 (file)
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Pleroma.Email do
   use Mix.Task
   import Mix.Pleroma
 
-  @shortdoc "Simple Email test"
+  @shortdoc "Email administrative tasks"
   @moduledoc File.read!("docs/administration/CLI_tasks/email.md")
 
   def run(["test" | args]) do
@@ -21,4 +21,21 @@ defmodule Mix.Tasks.Pleroma.Email do
 
     shell_info("Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}")
   end
+
+  def run(["resend_confirmation_emails"]) do
+    start_pleroma()
+
+    Pleroma.User.Query.build(%{
+      local: true,
+      deactivated: false,
+      confirmation_pending: true,
+      invisible: false
+    })
+    |> Pleroma.RepoStreamer.chunk_stream(500)
+    |> Stream.each(fn users ->
+      users
+      |> Enum.each(fn user -> Pleroma.User.try_send_confirmation_email(user) end)
+    end)
+    |> Stream.run()
+  end
 end
index b20c49d89538db746214e7b6b17256995d3879c8..8196e34b1dc28a6fbf6500b9fb7e4e532510d4fd 100644 (file)
@@ -196,17 +196,24 @@ defmodule Mix.Tasks.Pleroma.User do
       OptionParser.parse(
         rest,
         strict: [
-          moderator: :boolean,
           admin: :boolean,
-          locked: :boolean
+          confirmed: :boolean,
+          locked: :boolean,
+          moderator: :boolean
         ]
       )
 
     with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
       user =
-        case Keyword.get(options, :moderator) do
+        case Keyword.get(options, :admin) do
           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
 
       user =
@@ -216,9 +223,9 @@ defmodule Mix.Tasks.Pleroma.User do
         end
 
       _user =
-        case Keyword.get(options, :admin) do
+        case Keyword.get(options, :moderator) do
           nil -> user
-          value -> set_admin(user, value)
+          value -> set_moderator(user, value)
         end
     else
       _ ->
@@ -353,6 +360,42 @@ defmodule Mix.Tasks.Pleroma.User do
     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.RepoStreamer.chunk_stream(500)
+    |> 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.RepoStreamer.chunk_stream(500)
+    |> 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
     start_pleroma()
 
@@ -410,4 +453,15 @@ defmodule Mix.Tasks.Pleroma.User do
     shell_info("Locked status of #{user.nickname}: #{user.locked}")
     user
   end
+
+  defp set_confirmed(user, value) do
+    {:ok, user} =
+      case value do
+        true -> User.need_confirmation(user, false)
+        false -> User.need_confirmation(user, true)
+      end
+
+    shell_info("Confirmation pending status of #{user.nickname}: #{user.confirmation_pending}")
+    user
+  end
 end
index 410c9cbac1a67cd78fd0c13a6098c66e16262d4e..03be61ccf947b9e8fb2a391ef00d6be6f792e531 100644 (file)
@@ -813,7 +813,8 @@ defmodule Pleroma.User do
   def send_welcome_email(_), do: {:ok, :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
       send_confirmation_email(user)
       {:ok, :enqueued}
@@ -2071,6 +2072,13 @@ defmodule Pleroma.User do
     Enum.map(users, &toggle_confirmation/1)
   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
     mascot
   end
index 193b90d9d65e077d0b20d789afd843f62ff85758..2440bf890c1adf275b7341019f0e4738eb0bca46 100644 (file)
@@ -110,12 +110,12 @@ defmodule Pleroma.User.Query do
     where(query, [u], fragment("? && ?", u.tags, ^tags))
   end
 
-  defp compose_query({:is_admin, _}, query) do
-    where(query, [u], u.is_admin)
+  defp compose_query({:is_admin, bool}, query) do
+    where(query, [u], u.is_admin == ^bool)
   end
 
-  defp compose_query({:is_moderator, _}, query) do
-    where(query, [u], u.is_moderator)
+  defp compose_query({:is_moderator, bool}, query) do
+    where(query, [u], u.is_moderator == ^bool)
   end
 
   defp compose_query({:super_users, _}, query) do
@@ -148,6 +148,10 @@ defmodule Pleroma.User.Query do
     where(query, [u], u.deactivated == ^true)
   end
 
+  defp compose_query({:confirmation_pending, bool}, query) do
+    where(query, [u], u.confirmation_pending == ^bool)
+  end
+
   defp compose_query({:need_approval, _}, query) do
     where(query, [u], u.approval_pending)
   end
index ce43a9cc72947f58e3dcff327b08ee8393735057..b8c423c48455590765345bbc15faac0d68c441f7 100644 (file)
@@ -225,47 +225,64 @@ defmodule Mix.Tasks.Pleroma.UserTest do
     test "All statuses set" do
       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 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 message =~ ~r/Locked status .* true/
 
       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)
       assert user.is_moderator
       assert user.locked
       assert user.is_admin
+      refute user.confirmation_pending
     end
 
     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([
         "set",
         user.nickname,
-        "--no-moderator",
         "--no-admin",
-        "--no-locked"
+        "--no-confirmed",
+        "--no-locked",
+        "--no-moderator"
       ])
 
       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 message =~ ~r/Locked status .* false/
 
       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)
       refute user.is_moderator
       refute user.locked
       refute user.is_admin
+      assert user.confirmation_pending
     end
 
     test "no user to set status" do
@@ -554,4 +571,44 @@ defmodule Mix.Tasks.Pleroma.UserTest do
       assert message =~ "Could not change user tags"
     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