First
[anni] / lib / pleroma / web / admin_api / controllers / user_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.AdminAPI.UserController do
6   use Pleroma.Web, :controller
7
8   import Pleroma.Web.ControllerHelper,
9     only: [fetch_integer_param: 3]
10
11   alias Pleroma.ModerationLog
12   alias Pleroma.User
13   alias Pleroma.Web.ActivityPub.Builder
14   alias Pleroma.Web.ActivityPub.Pipeline
15   alias Pleroma.Web.AdminAPI
16   alias Pleroma.Web.AdminAPI.Search
17   alias Pleroma.Web.Plugs.OAuthScopesPlug
18
19   @users_page_size 50
20
21   plug(Pleroma.Web.ApiSpec.CastAndValidate)
22
23   plug(
24     OAuthScopesPlug,
25     %{scopes: ["admin:read:accounts"]}
26     when action in [:index, :show]
27   )
28
29   plug(
30     OAuthScopesPlug,
31     %{scopes: ["admin:write:accounts"]}
32     when action in [
33            :delete,
34            :create,
35            :toggle_activation,
36            :activate,
37            :deactivate,
38            :approve,
39            :suggest,
40            :unsuggest
41          ]
42   )
43
44   plug(
45     OAuthScopesPlug,
46     %{scopes: ["admin:write:follows"]}
47     when action in [:follow, :unfollow]
48   )
49
50   action_fallback(AdminAPI.FallbackController)
51
52   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.UserOperation
53
54   def delete(conn, %{nickname: nickname}) do
55     conn
56     |> Map.put(:body_params, %{nicknames: [nickname]})
57     |> delete(%{})
58   end
59
60   def delete(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
61     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
62
63     Enum.each(users, fn user ->
64       {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
65       Pipeline.common_pipeline(delete_data, local: true)
66     end)
67
68     ModerationLog.insert_log(%{
69       actor: admin,
70       subject: users,
71       action: "delete"
72     })
73
74     json(conn, nicknames)
75   end
76
77   def follow(
78         %{
79           assigns: %{user: admin},
80           body_params: %{
81             follower: follower_nick,
82             followed: followed_nick
83           }
84         } = conn,
85         _
86       ) do
87     with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
88          %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
89       User.follow(follower, followed)
90
91       ModerationLog.insert_log(%{
92         actor: admin,
93         followed: followed,
94         follower: follower,
95         action: "follow"
96       })
97     end
98
99     json(conn, "ok")
100   end
101
102   def unfollow(
103         %{
104           assigns: %{user: admin},
105           body_params: %{
106             follower: follower_nick,
107             followed: followed_nick
108           }
109         } = conn,
110         _
111       ) do
112     with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
113          %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
114       User.unfollow(follower, followed)
115
116       ModerationLog.insert_log(%{
117         actor: admin,
118         followed: followed,
119         follower: follower,
120         action: "unfollow"
121       })
122     end
123
124     json(conn, "ok")
125   end
126
127   def create(%{assigns: %{user: admin}, body_params: %{users: users}} = conn, _) do
128     changesets =
129       users
130       |> Enum.map(fn %{nickname: nickname, email: email, password: password} ->
131         user_data = %{
132           nickname: nickname,
133           name: nickname,
134           email: email,
135           password: password,
136           password_confirmation: password,
137           bio: "."
138         }
139
140         User.register_changeset(%User{}, user_data, need_confirmation: false)
141       end)
142       |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
143         Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
144       end)
145
146     case Pleroma.Repo.transaction(changesets) do
147       {:ok, users_map} ->
148         users =
149           users_map
150           |> Map.values()
151           |> Enum.map(fn user ->
152             {:ok, user} = User.post_register_action(user)
153
154             user
155           end)
156
157         ModerationLog.insert_log(%{
158           actor: admin,
159           subjects: users,
160           action: "create"
161         })
162
163         render(conn, "created_many.json", users: users)
164
165       {:error, id, changeset, _} ->
166         changesets =
167           Enum.map(changesets.operations, fn
168             {^id, {:changeset, _current_changeset, _}} ->
169               changeset
170
171             {_, {:changeset, current_changeset, _}} ->
172               current_changeset
173           end)
174
175         conn
176         |> put_status(:conflict)
177         |> render("create_errors.json", changesets: changesets)
178     end
179   end
180
181   def show(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
182     with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
183       render(conn, "show.json", %{user: user})
184     else
185       _ -> {:error, :not_found}
186     end
187   end
188
189   def toggle_activation(%{assigns: %{user: admin}} = conn, %{nickname: nickname}) do
190     user = User.get_cached_by_nickname(nickname)
191
192     {:ok, updated_user} = User.set_activation(user, !user.is_active)
193
194     action = if !user.is_active, do: "activate", else: "deactivate"
195
196     ModerationLog.insert_log(%{
197       actor: admin,
198       subject: [user],
199       action: action
200     })
201
202     render(conn, "show.json", user: updated_user)
203   end
204
205   def activate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
206     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
207     {:ok, updated_users} = User.set_activation(users, true)
208
209     ModerationLog.insert_log(%{
210       actor: admin,
211       subject: users,
212       action: "activate"
213     })
214
215     render(conn, "index.json", users: Keyword.values(updated_users))
216   end
217
218   def deactivate(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
219     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
220     {:ok, updated_users} = User.set_activation(users, false)
221
222     ModerationLog.insert_log(%{
223       actor: admin,
224       subject: users,
225       action: "deactivate"
226     })
227
228     render(conn, "index.json", users: Keyword.values(updated_users))
229   end
230
231   def approve(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
232     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
233     {:ok, updated_users} = User.approve(users)
234
235     ModerationLog.insert_log(%{
236       actor: admin,
237       subject: users,
238       action: "approve"
239     })
240
241     render(conn, "index.json", users: updated_users)
242   end
243
244   def suggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
245     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
246     {:ok, updated_users} = User.set_suggestion(users, true)
247
248     ModerationLog.insert_log(%{
249       actor: admin,
250       subject: users,
251       action: "add_suggestion"
252     })
253
254     render(conn, "index.json", users: updated_users)
255   end
256
257   def unsuggest(%{assigns: %{user: admin}, body_params: %{nicknames: nicknames}} = conn, _) do
258     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
259     {:ok, updated_users} = User.set_suggestion(users, false)
260
261     ModerationLog.insert_log(%{
262       actor: admin,
263       subject: users,
264       action: "remove_suggestion"
265     })
266
267     render(conn, "index.json", users: updated_users)
268   end
269
270   def index(conn, params) do
271     {page, page_size} = page_params(params)
272     filters = maybe_parse_filters(params[:filters])
273
274     search_params =
275       %{
276         query: params[:query],
277         page: page,
278         page_size: page_size,
279         tags: params[:tags],
280         name: params[:name],
281         email: params[:email],
282         actor_types: params[:actor_types]
283       }
284       |> Map.merge(filters)
285
286     with {:ok, users, count} <- Search.user(search_params) do
287       render(conn, "index.json", users: users, count: count, page_size: page_size)
288     end
289   end
290
291   @filters ~w(local external active deactivated need_approval unconfirmed is_admin is_moderator)
292
293   @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
294   defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
295
296   defp maybe_parse_filters(filters) do
297     filters
298     |> String.split(",")
299     |> Enum.filter(&Enum.member?(@filters, &1))
300     |> Map.new(&{String.to_existing_atom(&1), true})
301   end
302
303   defp page_params(params) do
304     {
305       fetch_integer_param(params, :page, 1),
306       fetch_integer_param(params, :page_size, @users_page_size)
307     }
308   end
309 end