Merge branch 'feature/confirm-user-acc-resend-confirmation' into 'develop'
authorlain <lain@soykaf.club>
Fri, 22 Nov 2019 15:39:19 +0000 (15:39 +0000)
committerlain <lain@soykaf.club>
Fri, 22 Nov 2019 15:39:19 +0000 (15:39 +0000)
AdminAPI: Confirm user account, resend confirmation email

See merge request pleroma/pleroma!1994

CHANGELOG.md
docs/API/admin_api.md
lib/pleroma/moderation_log.ex
lib/pleroma/user.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/admin_api/views/account_view.ex
lib/pleroma/web/router.ex
test/web/admin_api/admin_api_controller_test.exs

index 9d27dced14f44e6647f7d49e211a83f928e9d5d0..8db8039b842efdbd29c772aec076bab6e8aa1029 100644 (file)
@@ -70,6 +70,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Add the `recipients` parameter to `GET /api/v1/conversations`
 - Configuration: `feed` option for user atom feed.
 - Pleroma API: Add Emoji reactions
+- Admin API: `PATCH /api/pleroma/users/confirm_email` to confirm email for multiple users, `PATCH /api/pleroma/users/resend_confirmation_email` to resend confirmation email for multiple users
 </details>
 
 ### Fixed
index f64983a9041a9e479637737a4533e509f09a0331..2cac317def2df0790ae1ffd04db05ec0fd455c66 100644 (file)
@@ -870,3 +870,19 @@ Compile time settings (need instance reboot):
 - Authentication: required
 - Params: None
 - Response: JSON, "ok" and 200 status
+
+## `PATCH /api/pleroma/admin/users/confirm_email`
+
+### Confirm users' emails
+
+- Params:
+  - `nicknames`
+- Response: Array of user nicknames
+
+## `PATCH /api/pleroma/admin/users/resend_confirmation_email`
+
+### Resend confirmation email
+
+- Params:
+  - `nicknames`
+- Response: Array of user nicknames
index ffa5dc25db48a9bfa576a32d8658cf4f46169e27..706f089dc044f02c07d10ac2b621db30629bbf81 100644 (file)
@@ -624,7 +624,31 @@ defmodule Pleroma.ModerationLog do
           "subject" => subjects
         }
       }) do
-    "@#{actor_nickname} force password reset for users: #{users_to_nicknames_string(subjects)}"
+    "@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
+  end
+
+  @spec get_log_entry_message(ModerationLog) :: String.t()
+  def get_log_entry_message(%ModerationLog{
+        data: %{
+          "actor" => %{"nickname" => actor_nickname},
+          "action" => "confirm_email",
+          "subject" => subjects
+        }
+      }) do
+    "@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
+  end
+
+  @spec get_log_entry_message(ModerationLog) :: String.t()
+  def get_log_entry_message(%ModerationLog{
+        data: %{
+          "actor" => %{"nickname" => actor_nickname},
+          "action" => "resend_confirmation_email",
+          "subject" => subjects
+        }
+      }) do
+    "@#{actor_nickname} re-sent confirmation email for users: #{
+      users_to_nicknames_string(subjects)
+    }"
   end
 
   defp nicknames_to_string(nicknames) do
index b2c61e921d1fc1e492760851f2b9a8ff449974d4..3010fe87fd46976784a3cd1e24229fc344869c4a 100644 (file)
@@ -491,6 +491,10 @@ defmodule Pleroma.User do
     end
   end
 
+  def try_send_confirmation_email(users) do
+    Enum.each(users, &try_send_confirmation_email/1)
+  end
+
   def needs_update?(%User{local: true}), do: false
 
   def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
@@ -1582,6 +1586,11 @@ defmodule Pleroma.User do
     |> update_and_set_cache()
   end
 
+  @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
+  def toggle_confirmation(users) do
+    Enum.map(users, &toggle_confirmation/1)
+  end
+
   def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
     mascot
   end
index 8c1318d1bb2821f40c2797eedbe55e019637490a..c46ce76da304bd97dfbd3e7b8eac8740e42df869 100644 (file)
@@ -335,7 +335,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     }
 
     with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
-         {:ok, users, count} <- filter_relay_user(users, count),
+         {:ok, users, count} <- filter_service_users(users, count),
          do:
            conn
            |> json(
@@ -347,15 +347,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
            )
   end
 
-  defp filter_relay_user(users, count) do
-    filtered_users = Enum.reject(users, &relay_user?/1)
-    count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
+  defp filter_service_users(users, count) do
+    filtered_users = Enum.reject(users, &service_user?/1)
+    count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count
 
     {:ok, filtered_users, count}
   end
 
-  defp relay_user?(user) do
-    user.ap_id == Relay.relay_ap_id()
+  defp service_user?(user) do
+    String.match?(user.ap_id, ~r/.*\/relay$/) or
+      String.match?(user.ap_id, ~r/.*\/internal\/fetch$/)
   end
 
   @filters ~w(local external active deactivated is_admin is_moderator)
@@ -799,6 +800,34 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     conn |> json("ok")
   end
 
+  def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+    users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+
+    User.toggle_confirmation(users)
+
+    ModerationLog.insert_log(%{
+      actor: admin,
+      subject: users,
+      action: "confirm_email"
+    })
+
+    conn |> json("")
+  end
+
+  def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
+    users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
+
+    User.try_send_confirmation_email(users)
+
+    ModerationLog.insert_log(%{
+      actor: admin,
+      subject: users,
+      action: "resend_confirmation_email"
+    })
+
+    conn |> json("")
+  end
+
   def errors(conn, {:error, :not_found}) do
     conn
     |> put_status(:not_found)
index 6aa7257cead895b166043f343f10f26d278fbb9a..d9dba5c5181693044ea8df1bb9f70900d8bdca40 100644 (file)
@@ -36,7 +36,8 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
       "deactivated" => user.deactivated,
       "local" => user.local,
       "roles" => User.roles(user),
-      "tags" => user.tags || []
+      "tags" => user.tags || [],
+      "confirmation_pending" => user.confirmation_pending
     }
   end
 
index 129da422c2b3a4a13b9aee773859f2491910fe39..0b54f4ab7b665f1abb67c03f809a549853fa884c 100644 (file)
@@ -178,6 +178,9 @@ defmodule Pleroma.Web.Router do
     get("/users/:nickname", AdminAPIController, :user_show)
     get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
 
+    patch("/users/confirm_email", AdminAPIController, :confirm_email)
+    patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email)
+
     get("/reports", AdminAPIController, :list_reports)
     get("/grouped_reports", AdminAPIController, :list_grouped_reports)
     get("/reports/:id", AdminAPIController, :report_show)
index 3a4c4d65c9e118bc9426798f7041354b4c2e8606..9b4f359dc8bbf4fc60dfef468de24e3a46f348ec 100644 (file)
@@ -225,7 +225,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         "roles" => %{"admin" => false, "moderator" => false},
         "tags" => [],
         "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-        "display_name" => HTML.strip_tags(user.name || user.nickname)
+        "display_name" => HTML.strip_tags(user.name || user.nickname),
+        "confirmation_pending" => false
       }
 
       assert expected == json_response(conn, 200)
@@ -634,7 +635,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
             "local" => true,
             "tags" => [],
             "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
-            "display_name" => HTML.strip_tags(admin.name || admin.nickname)
+            "display_name" => HTML.strip_tags(admin.name || admin.nickname),
+            "confirmation_pending" => false
           },
           %{
             "deactivated" => user.deactivated,
@@ -644,7 +646,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
             "local" => false,
             "tags" => ["foo", "bar"],
             "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-            "display_name" => HTML.strip_tags(user.name || user.nickname)
+            "display_name" => HTML.strip_tags(user.name || user.nickname),
+            "confirmation_pending" => false
           }
         ]
         |> Enum.sort_by(& &1["nickname"])
@@ -685,7 +688,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "local" => true,
                    "tags" => [],
                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                   "display_name" => HTML.strip_tags(user.name || user.nickname)
+                   "display_name" => HTML.strip_tags(user.name || user.nickname),
+                   "confirmation_pending" => false
                  }
                ]
              }
@@ -709,7 +713,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "local" => true,
                    "tags" => [],
                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                   "display_name" => HTML.strip_tags(user.name || user.nickname)
+                   "display_name" => HTML.strip_tags(user.name || user.nickname),
+                   "confirmation_pending" => false
                  }
                ]
              }
@@ -733,7 +738,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "local" => true,
                    "tags" => [],
                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                   "display_name" => HTML.strip_tags(user.name || user.nickname)
+                   "display_name" => HTML.strip_tags(user.name || user.nickname),
+                   "confirmation_pending" => false
                  }
                ]
              }
@@ -757,7 +763,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "local" => true,
                    "tags" => [],
                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                   "display_name" => HTML.strip_tags(user.name || user.nickname)
+                   "display_name" => HTML.strip_tags(user.name || user.nickname),
+                   "confirmation_pending" => false
                  }
                ]
              }
@@ -781,7 +788,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "local" => true,
                    "tags" => [],
                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                   "display_name" => HTML.strip_tags(user.name || user.nickname)
+                   "display_name" => HTML.strip_tags(user.name || user.nickname),
+                   "confirmation_pending" => false
                  }
                ]
              }
@@ -805,7 +813,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "local" => true,
                    "tags" => [],
                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                   "display_name" => HTML.strip_tags(user.name || user.nickname)
+                   "display_name" => HTML.strip_tags(user.name || user.nickname),
+                   "confirmation_pending" => false
                  }
                ]
              }
@@ -824,7 +833,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "local" => true,
                    "tags" => [],
                    "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
-                   "display_name" => HTML.strip_tags(user2.name || user2.nickname)
+                   "display_name" => HTML.strip_tags(user2.name || user2.nickname),
+                   "confirmation_pending" => false
                  }
                ]
              }
@@ -853,7 +863,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "local" => true,
                    "tags" => [],
                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                   "display_name" => HTML.strip_tags(user.name || user.nickname)
+                   "display_name" => HTML.strip_tags(user.name || user.nickname),
+                   "confirmation_pending" => false
                  }
                ]
              }
@@ -880,7 +891,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
             "local" => true,
             "tags" => [],
             "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-            "display_name" => HTML.strip_tags(user.name || user.nickname)
+            "display_name" => HTML.strip_tags(user.name || user.nickname),
+            "confirmation_pending" => false
           },
           %{
             "deactivated" => admin.deactivated,
@@ -890,7 +902,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
             "local" => true,
             "tags" => [],
             "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
-            "display_name" => HTML.strip_tags(admin.name || admin.nickname)
+            "display_name" => HTML.strip_tags(admin.name || admin.nickname),
+            "confirmation_pending" => false
           },
           %{
             "deactivated" => false,
@@ -900,7 +913,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
             "roles" => %{"admin" => true, "moderator" => false},
             "tags" => [],
             "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(),
-            "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname)
+            "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname),
+            "confirmation_pending" => false
           }
         ]
         |> Enum.sort_by(& &1["nickname"])
@@ -929,7 +943,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
             "local" => admin.local,
             "tags" => [],
             "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
-            "display_name" => HTML.strip_tags(admin.name || admin.nickname)
+            "display_name" => HTML.strip_tags(admin.name || admin.nickname),
+            "confirmation_pending" => false
           },
           %{
             "deactivated" => false,
@@ -939,7 +954,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
             "local" => second_admin.local,
             "tags" => [],
             "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(),
-            "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname)
+            "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname),
+            "confirmation_pending" => false
           }
         ]
         |> Enum.sort_by(& &1["nickname"])
@@ -970,7 +986,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "local" => moderator.local,
                    "tags" => [],
                    "avatar" => User.avatar_url(moderator) |> MediaProxy.url(),
-                   "display_name" => HTML.strip_tags(moderator.name || moderator.nickname)
+                   "display_name" => HTML.strip_tags(moderator.name || moderator.nickname),
+                   "confirmation_pending" => false
                  }
                ]
              }
@@ -994,7 +1011,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
             "local" => user1.local,
             "tags" => ["first"],
             "avatar" => User.avatar_url(user1) |> MediaProxy.url(),
-            "display_name" => HTML.strip_tags(user1.name || user1.nickname)
+            "display_name" => HTML.strip_tags(user1.name || user1.nickname),
+            "confirmation_pending" => false
           },
           %{
             "deactivated" => false,
@@ -1004,7 +1022,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
             "local" => user2.local,
             "tags" => ["second"],
             "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
-            "display_name" => HTML.strip_tags(user2.name || user2.nickname)
+            "display_name" => HTML.strip_tags(user2.name || user2.nickname),
+            "confirmation_pending" => false
           }
         ]
         |> Enum.sort_by(& &1["nickname"])
@@ -1040,7 +1059,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "local" => user.local,
                    "tags" => [],
                    "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-                   "display_name" => HTML.strip_tags(user.name || user.nickname)
+                   "display_name" => HTML.strip_tags(user.name || user.nickname),
+                   "confirmation_pending" => false
                  }
                ]
              }
@@ -1066,7 +1086,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "local" => true,
                    "tags" => [],
                    "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
-                   "display_name" => HTML.strip_tags(admin.name || admin.nickname)
+                   "display_name" => HTML.strip_tags(admin.name || admin.nickname),
+                   "confirmation_pending" => false
                  }
                ]
              }
@@ -1135,7 +1156,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "local" => true,
                "tags" => [],
                "avatar" => User.avatar_url(user) |> MediaProxy.url(),
-               "display_name" => HTML.strip_tags(user.name || user.nickname)
+               "display_name" => HTML.strip_tags(user.name || user.nickname),
+               "confirmation_pending" => false
              }
 
     log_entry = Repo.one(ModerationLog)
@@ -2839,6 +2861,68 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin"
     end
   end
+
+  describe "PATCH /confirm_email" do
+    setup %{conn: conn} do
+      admin = insert(:user, is_admin: true)
+
+      %{conn: assign(conn, :user, admin), admin: admin}
+    end
+
+    test "it confirms emails of two users", %{admin: admin} do
+      [first_user, second_user] = insert_pair(:user, confirmation_pending: true)
+
+      assert first_user.confirmation_pending == true
+      assert second_user.confirmation_pending == true
+
+      build_conn()
+      |> assign(:user, admin)
+      |> patch("/api/pleroma/admin/users/confirm_email", %{
+        nicknames: [
+          first_user.nickname,
+          second_user.nickname
+        ]
+      })
+
+      assert first_user.confirmation_pending == true
+      assert second_user.confirmation_pending == true
+
+      log_entry = Repo.one(ModerationLog)
+
+      assert ModerationLog.get_log_entry_message(log_entry) ==
+               "@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{
+                 second_user.nickname
+               }"
+    end
+  end
+
+  describe "PATCH /resend_confirmation_email" do
+    setup %{conn: conn} do
+      admin = insert(:user, is_admin: true)
+
+      %{conn: assign(conn, :user, admin), admin: admin}
+    end
+
+    test "it resend emails for two users", %{admin: admin} do
+      [first_user, second_user] = insert_pair(:user, confirmation_pending: true)
+
+      build_conn()
+      |> assign(:user, admin)
+      |> patch("/api/pleroma/admin/users/resend_confirmation_email", %{
+        nicknames: [
+          first_user.nickname,
+          second_user.nickname
+        ]
+      })
+
+      log_entry = Repo.one(ModerationLog)
+
+      assert ModerationLog.get_log_entry_message(log_entry) ==
+               "@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{
+                 second_user.nickname
+               }"
+    end
+  end
 end
 
 # Needed for testing