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