Merge branch 'admin-api-users-sort' into 'develop'
[akkoma] / lib / pleroma / web / admin_api / controllers / admin_api_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.AdminAPI.AdminAPIController do
6 use Pleroma.Web, :controller
7
8 import Pleroma.Web.ControllerHelper,
9 only: [json_response: 3, fetch_integer_param: 3]
10
11 alias Pleroma.Config
12 alias Pleroma.MFA
13 alias Pleroma.ModerationLog
14 alias Pleroma.Stats
15 alias Pleroma.User
16 alias Pleroma.Web.ActivityPub.ActivityPub
17 alias Pleroma.Web.AdminAPI
18 alias Pleroma.Web.AdminAPI.AccountView
19 alias Pleroma.Web.AdminAPI.ModerationLogView
20 alias Pleroma.Web.Endpoint
21 alias Pleroma.Web.Plugs.OAuthScopesPlug
22 alias Pleroma.Web.Router
23
24 @users_page_size 50
25
26 plug(
27 OAuthScopesPlug,
28 %{scopes: ["admin:read:accounts"]}
29 when action in [:right_get, :show_user_credentials, :create_backup]
30 )
31
32 plug(
33 OAuthScopesPlug,
34 %{scopes: ["admin:write:accounts"]}
35 when action in [
36 :get_password_reset,
37 :force_password_reset,
38 :tag_users,
39 :untag_users,
40 :right_add,
41 :right_add_multiple,
42 :right_delete,
43 :disable_mfa,
44 :right_delete_multiple,
45 :update_user_credentials
46 ]
47 )
48
49 plug(
50 OAuthScopesPlug,
51 %{scopes: ["admin:read:statuses"]}
52 when action in [:list_user_statuses, :list_instance_statuses]
53 )
54
55 plug(
56 OAuthScopesPlug,
57 %{scopes: ["admin:read:chats"]}
58 when action in [:list_user_chats]
59 )
60
61 plug(
62 OAuthScopesPlug,
63 %{scopes: ["admin:read"]}
64 when action in [
65 :list_log,
66 :stats,
67 :need_reboot
68 ]
69 )
70
71 plug(
72 OAuthScopesPlug,
73 %{scopes: ["admin:write"]}
74 when action in [
75 :restart,
76 :resend_confirmation_email,
77 :confirm_email,
78 :reload_emoji
79 ]
80 )
81
82 action_fallback(AdminAPI.FallbackController)
83
84 def list_instance_statuses(conn, %{"instance" => instance} = params) do
85 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
86 {page, page_size} = page_params(params)
87
88 result =
89 ActivityPub.fetch_statuses(nil, %{
90 instance: instance,
91 limit: page_size,
92 offset: (page - 1) * page_size,
93 exclude_reblogs: not with_reblogs,
94 total: true
95 })
96
97 conn
98 |> put_view(AdminAPI.StatusView)
99 |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
100 end
101
102 def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
103 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
104 godmode = params["godmode"] == "true" || params["godmode"] == true
105
106 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
107 {page, page_size} = page_params(params)
108
109 result =
110 ActivityPub.fetch_user_activities(user, nil, %{
111 limit: page_size,
112 offset: (page - 1) * page_size,
113 godmode: godmode,
114 exclude_reblogs: not with_reblogs,
115 pagination_type: :offset,
116 total: true
117 })
118
119 conn
120 |> put_view(AdminAPI.StatusView)
121 |> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
122 else
123 _ -> {:error, :not_found}
124 end
125 end
126
127 def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
128 with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
129 chats =
130 Pleroma.Chat.for_user_query(user_id)
131 |> Pleroma.Repo.all()
132
133 conn
134 |> put_view(AdminAPI.ChatView)
135 |> render("index.json", chats: chats)
136 else
137 _ -> {:error, :not_found}
138 end
139 end
140
141 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
142 with {:ok, _} <- User.tag(nicknames, tags) do
143 ModerationLog.insert_log(%{
144 actor: admin,
145 nicknames: nicknames,
146 tags: tags,
147 action: "tag"
148 })
149
150 json_response(conn, :no_content, "")
151 end
152 end
153
154 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
155 with {:ok, _} <- User.untag(nicknames, tags) do
156 ModerationLog.insert_log(%{
157 actor: admin,
158 nicknames: nicknames,
159 tags: tags,
160 action: "untag"
161 })
162
163 json_response(conn, :no_content, "")
164 end
165 end
166
167 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
168 "permission_group" => permission_group,
169 "nicknames" => nicknames
170 })
171 when permission_group in ["moderator", "admin"] do
172 update = %{:"is_#{permission_group}" => true}
173
174 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
175
176 for u <- users, do: User.admin_api_update(u, update)
177
178 ModerationLog.insert_log(%{
179 action: "grant",
180 actor: admin,
181 subject: users,
182 permission: permission_group
183 })
184
185 json(conn, update)
186 end
187
188 def right_add_multiple(conn, _) do
189 render_error(conn, :not_found, "No such permission_group")
190 end
191
192 def right_add(%{assigns: %{user: admin}} = conn, %{
193 "permission_group" => permission_group,
194 "nickname" => nickname
195 })
196 when permission_group in ["moderator", "admin"] do
197 fields = %{:"is_#{permission_group}" => true}
198
199 {:ok, user} =
200 nickname
201 |> User.get_cached_by_nickname()
202 |> User.admin_api_update(fields)
203
204 ModerationLog.insert_log(%{
205 action: "grant",
206 actor: admin,
207 subject: [user],
208 permission: permission_group
209 })
210
211 json(conn, fields)
212 end
213
214 def right_add(conn, _) do
215 render_error(conn, :not_found, "No such permission_group")
216 end
217
218 def right_get(conn, %{"nickname" => nickname}) do
219 user = User.get_cached_by_nickname(nickname)
220
221 conn
222 |> json(%{
223 is_moderator: user.is_moderator,
224 is_admin: user.is_admin
225 })
226 end
227
228 def right_delete_multiple(
229 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
230 %{
231 "permission_group" => permission_group,
232 "nicknames" => nicknames
233 }
234 )
235 when permission_group in ["moderator", "admin"] do
236 with false <- Enum.member?(nicknames, admin_nickname) do
237 update = %{:"is_#{permission_group}" => false}
238
239 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
240
241 for u <- users, do: User.admin_api_update(u, update)
242
243 ModerationLog.insert_log(%{
244 action: "revoke",
245 actor: admin,
246 subject: users,
247 permission: permission_group
248 })
249
250 json(conn, update)
251 else
252 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
253 end
254 end
255
256 def right_delete_multiple(conn, _) do
257 render_error(conn, :not_found, "No such permission_group")
258 end
259
260 def right_delete(
261 %{assigns: %{user: admin}} = conn,
262 %{
263 "permission_group" => permission_group,
264 "nickname" => nickname
265 }
266 )
267 when permission_group in ["moderator", "admin"] do
268 fields = %{:"is_#{permission_group}" => false}
269
270 {:ok, user} =
271 nickname
272 |> User.get_cached_by_nickname()
273 |> User.admin_api_update(fields)
274
275 ModerationLog.insert_log(%{
276 action: "revoke",
277 actor: admin,
278 subject: [user],
279 permission: permission_group
280 })
281
282 json(conn, fields)
283 end
284
285 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
286 render_error(conn, :forbidden, "You can't revoke your own admin status.")
287 end
288
289 @doc "Get a password reset token (base64 string) for given nickname"
290 def get_password_reset(conn, %{"nickname" => nickname}) do
291 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
292 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
293
294 conn
295 |> json(%{
296 token: token.token,
297 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
298 })
299 end
300
301 @doc "Force password reset for a given user"
302 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
303 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
304
305 Enum.each(users, &User.force_password_reset_async/1)
306
307 ModerationLog.insert_log(%{
308 actor: admin,
309 subject: users,
310 action: "force_password_reset"
311 })
312
313 json_response(conn, :no_content, "")
314 end
315
316 @doc "Disable mfa for user's account."
317 def disable_mfa(conn, %{"nickname" => nickname}) do
318 case User.get_by_nickname(nickname) do
319 %User{} = user ->
320 MFA.disable(user)
321 json(conn, nickname)
322
323 _ ->
324 {:error, :not_found}
325 end
326 end
327
328 @doc "Show a given user's credentials"
329 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
330 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
331 conn
332 |> put_view(AccountView)
333 |> render("credentials.json", %{user: user, for: admin})
334 else
335 _ -> {:error, :not_found}
336 end
337 end
338
339 @doc "Updates a given user"
340 def update_user_credentials(
341 %{assigns: %{user: admin}} = conn,
342 %{"nickname" => nickname} = params
343 ) do
344 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
345 {:ok, _user} <-
346 User.update_as_admin(user, params) do
347 ModerationLog.insert_log(%{
348 actor: admin,
349 subject: [user],
350 action: "updated_users"
351 })
352
353 if params["password"] do
354 User.force_password_reset_async(user)
355 end
356
357 ModerationLog.insert_log(%{
358 actor: admin,
359 subject: [user],
360 action: "force_password_reset"
361 })
362
363 json(conn, %{status: "success"})
364 else
365 {:error, changeset} ->
366 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
367
368 {:errors, errors}
369
370 _ ->
371 {:error, :not_found}
372 end
373 end
374
375 def list_log(conn, params) do
376 {page, page_size} = page_params(params)
377
378 log =
379 ModerationLog.get_all(%{
380 page: page,
381 page_size: page_size,
382 start_date: params["start_date"],
383 end_date: params["end_date"],
384 user_id: params["user_id"],
385 search: params["search"]
386 })
387
388 conn
389 |> put_view(ModerationLogView)
390 |> render("index.json", %{log: log})
391 end
392
393 def restart(conn, _params) do
394 with :ok <- configurable_from_database() do
395 Restarter.Pleroma.restart(Config.get(:env), 50)
396
397 json(conn, %{})
398 end
399 end
400
401 def need_reboot(conn, _params) do
402 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
403 end
404
405 defp configurable_from_database do
406 if Config.get(:configurable_from_database) do
407 :ok
408 else
409 {:error, "You must enable configurable_from_database in your config file."}
410 end
411 end
412
413 def reload_emoji(conn, _params) do
414 Pleroma.Emoji.reload()
415
416 json(conn, "ok")
417 end
418
419 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
420 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
421
422 User.confirm(users)
423
424 ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
425
426 json(conn, "")
427 end
428
429 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
430 users =
431 Enum.map(nicknames, fn nickname ->
432 nickname
433 |> User.get_cached_by_nickname()
434 |> User.send_confirmation_email()
435 end)
436
437 ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
438
439 json(conn, "")
440 end
441
442 def stats(conn, params) do
443 counters = Stats.get_status_visibility_count(params["instance"])
444
445 json(conn, %{"status_visibility" => counters})
446 end
447
448 def create_backup(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
449 with %User{} = user <- User.get_by_nickname(nickname),
450 {:ok, _} <- Pleroma.User.Backup.create(user, admin.id) do
451 ModerationLog.insert_log(%{actor: admin, subject: user, action: "create_backup"})
452
453 json(conn, "")
454 end
455 end
456
457 defp page_params(params) do
458 {
459 fetch_integer_param(params, "page", 1),
460 fetch_integer_param(params, "page_size", @users_page_size)
461 }
462 end
463 end