Merge branch 'develop' into feature/return-link-for-password-reset
[akkoma] / lib / pleroma / web / admin_api / admin_api_controller.ex
index 2d3d0adc44f63d8fd9a7c3dcafb71778f4f44207..4421b30c8d533332053b87f31781e60eba49bc8a 100644 (file)
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   use Pleroma.Web, :controller
   alias Pleroma.Activity
+  alias Pleroma.ModerationLog
   alias Pleroma.User
   alias Pleroma.UserInviteToken
   alias Pleroma.Web.ActivityPub.ActivityPub
@@ -12,6 +13,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   alias Pleroma.Web.AdminAPI.AccountView
   alias Pleroma.Web.AdminAPI.Config
   alias Pleroma.Web.AdminAPI.ConfigView
+  alias Pleroma.Web.AdminAPI.ModerationLogView
   alias Pleroma.Web.AdminAPI.ReportView
   alias Pleroma.Web.AdminAPI.Search
   alias Pleroma.Web.CommonAPI
@@ -25,52 +27,113 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
   action_fallback(:errors)
 
-  def user_delete(conn, %{"nickname" => nickname}) do
-    User.get_cached_by_nickname(nickname)
-    |> User.delete()
+  def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
+    user = User.get_cached_by_nickname(nickname)
+    User.delete(user)
+
+    ModerationLog.insert_log(%{
+      actor: admin,
+      subject: user,
+      action: "delete"
+    })
 
     conn
     |> json(nickname)
   end
 
-  def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
+  def user_follow(%{assigns: %{user: admin}} = conn, %{
+        "follower" => follower_nick,
+        "followed" => followed_nick
+      }) do
     with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
          %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
       User.follow(follower, followed)
+
+      ModerationLog.insert_log(%{
+        actor: admin,
+        followed: followed,
+        follower: follower,
+        action: "follow"
+      })
     end
 
     conn
     |> json("ok")
   end
 
-  def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
+  def user_unfollow(%{assigns: %{user: admin}} = conn, %{
+        "follower" => follower_nick,
+        "followed" => followed_nick
+      }) do
     with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
          %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
       User.unfollow(follower, followed)
+
+      ModerationLog.insert_log(%{
+        actor: admin,
+        followed: followed,
+        follower: follower,
+        action: "unfollow"
+      })
     end
 
     conn
     |> json("ok")
   end
 
-  def user_create(
-        conn,
-        %{"nickname" => nickname, "email" => email, "password" => password}
-      ) do
-    user_data = %{
-      nickname: nickname,
-      name: nickname,
-      email: email,
-      password: password,
-      password_confirmation: password,
-      bio: "."
-    }
+  def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
+    changesets =
+      Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
+        user_data = %{
+          nickname: nickname,
+          name: nickname,
+          email: email,
+          password: password,
+          password_confirmation: password,
+          bio: "."
+        }
 
-    changeset = User.register_changeset(%User{}, user_data, need_confirmation: false)
-    {:ok, user} = User.register(changeset)
+        User.register_changeset(%User{}, user_data, need_confirmation: false)
+      end)
+      |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
+        Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
+      end)
+
+    case Pleroma.Repo.transaction(changesets) do
+      {:ok, users} ->
+        res =
+          users
+          |> Map.values()
+          |> Enum.map(fn user ->
+            {:ok, user} = User.post_register_action(user)
+
+            user
+          end)
+          |> Enum.map(&AccountView.render("created.json", %{user: &1}))
 
-    conn
-    |> json(user.nickname)
+        ModerationLog.insert_log(%{
+          actor: admin,
+          subjects: Map.values(users),
+          action: "create"
+        })
+
+        conn
+        |> json(res)
+
+      {:error, id, changeset, _} ->
+        res =
+          Enum.map(changesets.operations, fn
+            {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
+              AccountView.render("create-error.json", %{changeset: changeset})
+
+            {_, {:changeset, current_changeset, _}} ->
+              AccountView.render("create-error.json", %{changeset: current_changeset})
+          end)
+
+        conn
+        |> put_status(:conflict)
+        |> json(res)
+    end
   end
 
   def user_show(conn, %{"nickname" => nickname}) do
@@ -101,23 +164,47 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     end
   end
 
-  def user_toggle_activation(conn, %{"nickname" => nickname}) do
+  def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
     user = User.get_cached_by_nickname(nickname)
 
     {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
 
+    action = if user.info.deactivated, do: "activate", else: "deactivate"
+
+    ModerationLog.insert_log(%{
+      actor: admin,
+      subject: user,
+      action: action
+    })
+
     conn
     |> json(AccountView.render("show.json", %{user: updated_user}))
   end
 
-  def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
-    with {:ok, _} <- User.tag(nicknames, tags),
-         do: json_response(conn, :no_content, "")
+  def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
+    with {:ok, _} <- User.tag(nicknames, tags) do
+      ModerationLog.insert_log(%{
+        actor: admin,
+        nicknames: nicknames,
+        tags: tags,
+        action: "tag"
+      })
+
+      json_response(conn, :no_content, "")
+    end
   end
 
-  def untag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
-    with {:ok, _} <- User.untag(nicknames, tags),
-         do: json_response(conn, :no_content, "")
+  def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
+    with {:ok, _} <- User.untag(nicknames, tags) do
+      ModerationLog.insert_log(%{
+        actor: admin,
+        nicknames: nicknames,
+        tags: tags,
+        action: "untag"
+      })
+
+      json_response(conn, :no_content, "")
+    end
   end
 
   def list_users(conn, params) do
@@ -158,7 +245,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     |> Enum.into(%{}, &{&1, true})
   end
 
-  def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
+  def right_add(%{assigns: %{user: admin}} = conn, %{
+        "permission_group" => permission_group,
+        "nickname" => nickname
+      })
       when permission_group in ["moderator", "admin"] do
     user = User.get_cached_by_nickname(nickname)
 
@@ -173,6 +263,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
       |> Ecto.Changeset.change()
       |> Ecto.Changeset.put_embed(:info, info_cng)
 
+    ModerationLog.insert_log(%{
+      action: "grant",
+      actor: admin,
+      subject: user,
+      permission: permission_group
+    })
+
     {:ok, _user} = User.update_and_set_cache(cng)
 
     json(conn, info)
@@ -193,7 +290,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   end
 
   def right_delete(
-        %{assigns: %{user: %User{:nickname => admin_nickname}}} = conn,
+        %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
         %{
           "permission_group" => permission_group,
           "nickname" => nickname
@@ -217,6 +314,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
       {:ok, _user} = User.update_and_set_cache(cng)
 
+      ModerationLog.insert_log(%{
+        action: "revoke",
+        actor: admin,
+        subject: user,
+        permission: permission_group
+      })
+
       json(conn, info)
     end
   end
@@ -225,15 +329,33 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     render_error(conn, :not_found, "No such permission_group")
   end
 
-  def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do
+  def set_activation_status(%{assigns: %{user: admin}} = conn, %{
+        "nickname" => nickname,
+        "status" => status
+      }) do
     with {:ok, status} <- Ecto.Type.cast(:boolean, status),
          %User{} = user <- User.get_cached_by_nickname(nickname),
-         {:ok, _} <- User.deactivate(user, !status),
-         do: json_response(conn, :no_content, "")
+         {:ok, _} <- User.deactivate(user, !status) do
+      action = if(user.info.deactivated, do: "activate", else: "deactivate")
+
+      ModerationLog.insert_log(%{
+        actor: admin,
+        subject: user,
+        action: action
+      })
+
+      json_response(conn, :no_content, "")
+    end
   end
 
-  def relay_follow(conn, %{"relay_url" => target}) do
+  def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
     with {:ok, _message} <- Relay.follow(target) do
+      ModerationLog.insert_log(%{
+        action: "relay_follow",
+        actor: admin,
+        target: target
+      })
+
       json(conn, target)
     else
       _ ->
@@ -243,8 +365,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     end
   end
 
-  def relay_unfollow(conn, %{"relay_url" => target}) do
+  def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
     with {:ok, _message} <- Relay.unfollow(target) do
+      ModerationLog.insert_log(%{
+        action: "relay_unfollow",
+        actor: admin,
+        target: target
+      })
+
       json(conn, target)
     else
       _ ->
@@ -304,9 +432,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   def get_password_reset(conn, %{"nickname" => nickname}) do
     (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
     {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
+    host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
+    protocol = Pleroma.Config.get([Pleroma.Web.Endpoint, :protocol])
 
     conn
-    |> json(token.token)
+    |> json(%{
+      token: token.token,
+      link: "#{protocol}://#{host}/api/pleroma/password_reset/#{token}"
+    })
   end
 
   def list_reports(conn, params) do
@@ -314,11 +447,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
       params
       |> Map.put("type", "Flag")
       |> Map.put("skip_preload", true)
+      |> Map.put("total", true)
 
-    reports =
-      []
-      |> ActivityPub.fetch_activities(params)
-      |> Enum.reverse()
+    reports = ActivityPub.fetch_activities([], params)
 
     conn
     |> put_view(ReportView)
@@ -335,8 +466,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     end
   end
 
-  def report_update_state(conn, %{"id" => id, "state" => state}) do
+  def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
     with {:ok, report} <- CommonAPI.update_report_state(id, state) do
+      ModerationLog.insert_log(%{
+        action: "report_update",
+        actor: admin,
+        subject: report
+      })
+
       conn
       |> put_view(ReportView)
       |> render("show.json", %{report: report})
@@ -353,6 +490,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
       {:ok, activity} = CommonAPI.post(user, params)
 
+      ModerationLog.insert_log(%{
+        action: "report_response",
+        actor: user,
+        subject: activity,
+        text: params["status"]
+      })
+
       conn
       |> put_view(StatusView)
       |> render("status.json", %{activity: activity})
@@ -365,8 +509,18 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     end
   end
 
-  def status_update(conn, %{"id" => id} = params) do
+  def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
     with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
+      {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
+
+      ModerationLog.insert_log(%{
+        action: "status_update",
+        actor: admin,
+        subject: activity,
+        sensitive: sensitive,
+        visibility: params["visibility"]
+      })
+
       conn
       |> put_view(StatusView)
       |> render("status.json", %{activity: activity})
@@ -375,10 +529,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
   def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
     with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
+      ModerationLog.insert_log(%{
+        action: "status_delete",
+        actor: user,
+        subject_id: id
+      })
+
       json(conn, %{})
     end
   end
 
+  def list_log(conn, params) do
+    {page, page_size} = page_params(params)
+
+    log = ModerationLog.get_all(page, page_size)
+
+    conn
+    |> put_view(ModerationLogView)
+    |> render("index.json", %{log: log})
+  end
+
   def migrate_to_db(conn, _params) do
     Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
     json(conn, %{})