total rebase
[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, replace_params: false)
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(%{private: %{open_api_spex: %{params: %{nickname: nickname}}}} = conn, _) do
55     conn
56     |> do_deletes([nickname])
57   end
58
59   def delete(
60         %{
61           private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
62         } = conn,
63         _
64       ) do
65     conn
66     |> do_deletes(nicknames)
67   end
68
69   defp do_deletes(%{assigns: %{user: admin}} = conn, nicknames) when is_list(nicknames) do
70     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
71
72     Enum.each(users, fn user ->
73       {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
74       Pipeline.common_pipeline(delete_data, local: true)
75     end)
76
77     ModerationLog.insert_log(%{
78       actor: admin,
79       subject: users,
80       action: "delete"
81     })
82
83     json(conn, nicknames)
84   end
85
86   def follow(
87         %{
88           assigns: %{user: admin},
89           private: %{
90             open_api_spex: %{
91               body_params: %{
92                 follower: follower_nick,
93                 followed: followed_nick
94               }
95             }
96           }
97         } = conn,
98         _
99       ) do
100     with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
101          %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
102       User.follow(follower, followed)
103
104       ModerationLog.insert_log(%{
105         actor: admin,
106         followed: followed,
107         follower: follower,
108         action: "follow"
109       })
110     end
111
112     json(conn, "ok")
113   end
114
115   def unfollow(
116         %{
117           assigns: %{user: admin},
118           private: %{
119             open_api_spex: %{
120               body_params: %{
121                 follower: follower_nick,
122                 followed: followed_nick
123               }
124             }
125           }
126         } = conn,
127         _
128       ) do
129     with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
130          %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
131       User.unfollow(follower, followed)
132
133       ModerationLog.insert_log(%{
134         actor: admin,
135         followed: followed,
136         follower: follower,
137         action: "unfollow"
138       })
139     end
140
141     json(conn, "ok")
142   end
143
144   def create(
145         %{
146           assigns: %{user: admin},
147           private: %{open_api_spex: %{body_params: %{users: users}}}
148         } = conn,
149         _
150       ) do
151     changesets =
152       users
153       |> Enum.map(fn %{nickname: nickname, email: email, password: password} ->
154         user_data = %{
155           nickname: nickname,
156           name: nickname,
157           email: email,
158           password: password,
159           password_confirmation: password,
160           bio: "."
161         }
162
163         User.register_changeset(%User{}, user_data, need_confirmation: false)
164       end)
165       |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
166         Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
167       end)
168
169     case Pleroma.Repo.transaction(changesets) do
170       {:ok, users_map} ->
171         users =
172           users_map
173           |> Map.values()
174           |> Enum.map(fn user ->
175             {:ok, user} = User.post_register_action(user)
176
177             user
178           end)
179
180         ModerationLog.insert_log(%{
181           actor: admin,
182           subjects: users,
183           action: "create"
184         })
185
186         render(conn, "created_many.json", users: users)
187
188       {:error, id, changeset, _} ->
189         changesets =
190           Enum.map(changesets.operations, fn
191             {^id, {:changeset, _current_changeset, _}} ->
192               changeset
193
194             {_, {:changeset, current_changeset, _}} ->
195               current_changeset
196           end)
197
198         conn
199         |> put_status(:conflict)
200         |> render("create_errors.json", changesets: changesets)
201     end
202   end
203
204   def show(
205         %{
206           assigns: %{user: admin},
207           private: %{open_api_spex: %{params: %{nickname: nickname}}}
208         } = conn,
209         _
210       ) do
211     with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
212       render(conn, "show.json", %{user: user})
213     else
214       _ -> {:error, :not_found}
215     end
216   end
217
218   def toggle_activation(
219         %{assigns: %{user: admin}, private: %{open_api_spex: %{params: %{nickname: nickname}}}} =
220           conn,
221         _
222       ) do
223     user = User.get_cached_by_nickname(nickname)
224
225     {:ok, updated_user} = User.set_activation(user, !user.is_active)
226
227     action = if !user.is_active, do: "activate", else: "deactivate"
228
229     ModerationLog.insert_log(%{
230       actor: admin,
231       subject: [user],
232       action: action
233     })
234
235     render(conn, "show.json", user: updated_user)
236   end
237
238   def activate(
239         %{
240           assigns: %{user: admin},
241           private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
242         } = conn,
243         _
244       ) do
245     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
246     {:ok, updated_users} = User.set_activation(users, true)
247
248     ModerationLog.insert_log(%{
249       actor: admin,
250       subject: users,
251       action: "activate"
252     })
253
254     render(conn, "index.json", users: updated_users)
255   end
256
257   def deactivate(
258         %{
259           assigns: %{user: admin},
260           private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
261         } = conn,
262         _
263       ) do
264     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
265     {:ok, updated_users} = User.set_activation(users, false)
266
267     ModerationLog.insert_log(%{
268       actor: admin,
269       subject: users,
270       action: "deactivate"
271     })
272
273     render(conn, "index.json", users: updated_users)
274   end
275
276   def approve(
277         %{
278           assigns: %{user: admin},
279           private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
280         } = conn,
281         _
282       ) do
283     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
284     {:ok, updated_users} = User.approve(users)
285
286     ModerationLog.insert_log(%{
287       actor: admin,
288       subject: users,
289       action: "approve"
290     })
291
292     render(conn, "index.json", users: updated_users)
293   end
294
295   def suggest(
296         %{
297           assigns: %{user: admin},
298           private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
299         } = conn,
300         _
301       ) do
302     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
303     {:ok, updated_users} = User.set_suggestion(users, true)
304
305     ModerationLog.insert_log(%{
306       actor: admin,
307       subject: users,
308       action: "add_suggestion"
309     })
310
311     render(conn, "index.json", users: updated_users)
312   end
313
314   def unsuggest(
315         %{
316           assigns: %{user: admin},
317           private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
318         } = conn,
319         _
320       ) do
321     users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
322     {:ok, updated_users} = User.set_suggestion(users, false)
323
324     ModerationLog.insert_log(%{
325       actor: admin,
326       subject: users,
327       action: "remove_suggestion"
328     })
329
330     render(conn, "index.json", users: updated_users)
331   end
332
333   def index(%{private: %{open_api_spex: %{params: params}}} = conn, _) do
334     {page, page_size} = page_params(params)
335     filters = maybe_parse_filters(params[:filters])
336
337     search_params =
338       %{
339         query: params[:query],
340         page: page,
341         page_size: page_size,
342         tags: params[:tags],
343         name: params[:name],
344         email: params[:email],
345         actor_types: params[:actor_types]
346       }
347       |> Map.merge(filters)
348
349     with {:ok, users, count} <- Search.user(search_params) do
350       render(conn, "index.json", users: users, count: count, page_size: page_size)
351     end
352   end
353
354   @filters ~w(local external active deactivated need_approval unconfirmed is_admin is_moderator)
355
356   @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
357   defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
358
359   defp maybe_parse_filters(filters) do
360     filters
361     |> String.split(",")
362     |> Enum.filter(&Enum.member?(@filters, &1))
363     |> Map.new(&{String.to_existing_atom(&1), true})
364   end
365
366   defp page_params(params) do
367     {
368       fetch_integer_param(params, :page, 1),
369       fetch_integer_param(params, :page_size, @users_page_size)
370     }
371   end
372 end