463dd327aeaa4547c09327ef9641e995f58a65f9
[akkoma] / lib / pleroma / web / admin_api / admin_api_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 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 alias Pleroma.Activity
8 alias Pleroma.ModerationLog
9 alias Pleroma.User
10 alias Pleroma.UserInviteToken
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.Relay
13 alias Pleroma.Web.ActivityPub.Utils
14 alias Pleroma.Web.AdminAPI.AccountView
15 alias Pleroma.Web.AdminAPI.Config
16 alias Pleroma.Web.AdminAPI.ConfigView
17 alias Pleroma.Web.AdminAPI.ModerationLogView
18 alias Pleroma.Web.AdminAPI.Report
19 alias Pleroma.Web.AdminAPI.ReportView
20 alias Pleroma.Web.AdminAPI.Search
21 alias Pleroma.Web.CommonAPI
22 alias Pleroma.Web.Endpoint
23 alias Pleroma.Web.MastodonAPI.StatusView
24 alias Pleroma.Web.Router
25
26 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
27
28 require Logger
29
30 @users_page_size 50
31
32 action_fallback(:errors)
33
34 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
35 user = User.get_cached_by_nickname(nickname)
36 User.delete(user)
37
38 ModerationLog.insert_log(%{
39 actor: admin,
40 subject: user,
41 action: "delete"
42 })
43
44 conn
45 |> json(nickname)
46 end
47
48 def user_follow(%{assigns: %{user: admin}} = conn, %{
49 "follower" => follower_nick,
50 "followed" => followed_nick
51 }) do
52 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
53 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
54 User.follow(follower, followed)
55
56 ModerationLog.insert_log(%{
57 actor: admin,
58 followed: followed,
59 follower: follower,
60 action: "follow"
61 })
62 end
63
64 conn
65 |> json("ok")
66 end
67
68 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
69 "follower" => follower_nick,
70 "followed" => followed_nick
71 }) do
72 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
73 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
74 User.unfollow(follower, followed)
75
76 ModerationLog.insert_log(%{
77 actor: admin,
78 followed: followed,
79 follower: follower,
80 action: "unfollow"
81 })
82 end
83
84 conn
85 |> json("ok")
86 end
87
88 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
89 changesets =
90 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
91 user_data = %{
92 nickname: nickname,
93 name: nickname,
94 email: email,
95 password: password,
96 password_confirmation: password,
97 bio: "."
98 }
99
100 User.register_changeset(%User{}, user_data, need_confirmation: false)
101 end)
102 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
103 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
104 end)
105
106 case Pleroma.Repo.transaction(changesets) do
107 {:ok, users} ->
108 res =
109 users
110 |> Map.values()
111 |> Enum.map(fn user ->
112 {:ok, user} = User.post_register_action(user)
113
114 user
115 end)
116 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
117
118 ModerationLog.insert_log(%{
119 actor: admin,
120 subjects: Map.values(users),
121 action: "create"
122 })
123
124 conn
125 |> json(res)
126
127 {:error, id, changeset, _} ->
128 res =
129 Enum.map(changesets.operations, fn
130 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
131 AccountView.render("create-error.json", %{changeset: changeset})
132
133 {_, {:changeset, current_changeset, _}} ->
134 AccountView.render("create-error.json", %{changeset: current_changeset})
135 end)
136
137 conn
138 |> put_status(:conflict)
139 |> json(res)
140 end
141 end
142
143 def user_show(conn, %{"nickname" => nickname}) do
144 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
145 conn
146 |> put_view(AccountView)
147 |> render("show.json", %{user: user})
148 else
149 _ -> {:error, :not_found}
150 end
151 end
152
153 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
154 godmode = params["godmode"] == "true" || params["godmode"] == true
155
156 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
157 {_, page_size} = page_params(params)
158
159 activities =
160 ActivityPub.fetch_user_activities(user, nil, %{
161 "limit" => page_size,
162 "godmode" => godmode
163 })
164
165 conn
166 |> put_view(StatusView)
167 |> render("index.json", %{activities: activities, as: :activity})
168 else
169 _ -> {:error, :not_found}
170 end
171 end
172
173 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
174 user = User.get_cached_by_nickname(nickname)
175
176 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
177
178 action = if user.info.deactivated, do: "activate", else: "deactivate"
179
180 ModerationLog.insert_log(%{
181 actor: admin,
182 subject: user,
183 action: action
184 })
185
186 conn
187 |> put_view(AccountView)
188 |> render("show.json", %{user: updated_user})
189 end
190
191 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
192 with {:ok, _} <- User.tag(nicknames, tags) do
193 ModerationLog.insert_log(%{
194 actor: admin,
195 nicknames: nicknames,
196 tags: tags,
197 action: "tag"
198 })
199
200 json_response(conn, :no_content, "")
201 end
202 end
203
204 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
205 with {:ok, _} <- User.untag(nicknames, tags) do
206 ModerationLog.insert_log(%{
207 actor: admin,
208 nicknames: nicknames,
209 tags: tags,
210 action: "untag"
211 })
212
213 json_response(conn, :no_content, "")
214 end
215 end
216
217 def list_users(conn, params) do
218 {page, page_size} = page_params(params)
219 filters = maybe_parse_filters(params["filters"])
220
221 search_params = %{
222 query: params["query"],
223 page: page,
224 page_size: page_size,
225 tags: params["tags"],
226 name: params["name"],
227 email: params["email"]
228 }
229
230 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
231 do:
232 conn
233 |> json(
234 AccountView.render("index.json",
235 users: users,
236 count: count,
237 page_size: page_size
238 )
239 )
240 end
241
242 @filters ~w(local external active deactivated is_admin is_moderator)
243
244 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
245 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
246
247 defp maybe_parse_filters(filters) do
248 filters
249 |> String.split(",")
250 |> Enum.filter(&Enum.member?(@filters, &1))
251 |> Enum.map(&String.to_atom(&1))
252 |> Enum.into(%{}, &{&1, true})
253 end
254
255 def right_add(%{assigns: %{user: admin}} = conn, %{
256 "permission_group" => permission_group,
257 "nickname" => nickname
258 })
259 when permission_group in ["moderator", "admin"] do
260 info = Map.put(%{}, "is_" <> permission_group, true)
261
262 {:ok, user} =
263 nickname
264 |> User.get_cached_by_nickname()
265 |> User.update_info(&User.Info.admin_api_update(&1, info))
266
267 ModerationLog.insert_log(%{
268 action: "grant",
269 actor: admin,
270 subject: user,
271 permission: permission_group
272 })
273
274 json(conn, info)
275 end
276
277 def right_add(conn, _) do
278 render_error(conn, :not_found, "No such permission_group")
279 end
280
281 def right_get(conn, %{"nickname" => nickname}) do
282 user = User.get_cached_by_nickname(nickname)
283
284 conn
285 |> json(%{
286 is_moderator: user.info.is_moderator,
287 is_admin: user.info.is_admin
288 })
289 end
290
291 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
292 render_error(conn, :forbidden, "You can't revoke your own admin status.")
293 end
294
295 def right_delete(
296 %{assigns: %{user: admin}} = conn,
297 %{
298 "permission_group" => permission_group,
299 "nickname" => nickname
300 }
301 )
302 when permission_group in ["moderator", "admin"] do
303 info = Map.put(%{}, "is_" <> permission_group, false)
304
305 {:ok, user} =
306 nickname
307 |> User.get_cached_by_nickname()
308 |> User.update_info(&User.Info.admin_api_update(&1, info))
309
310 ModerationLog.insert_log(%{
311 action: "revoke",
312 actor: admin,
313 subject: user,
314 permission: permission_group
315 })
316
317 json(conn, info)
318 end
319
320 def right_delete(conn, _) do
321 render_error(conn, :not_found, "No such permission_group")
322 end
323
324 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
325 "nickname" => nickname,
326 "status" => status
327 }) do
328 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
329 %User{} = user <- User.get_cached_by_nickname(nickname),
330 {:ok, _} <- User.deactivate(user, !status) do
331 action = if(user.info.deactivated, do: "activate", else: "deactivate")
332
333 ModerationLog.insert_log(%{
334 actor: admin,
335 subject: user,
336 action: action
337 })
338
339 json_response(conn, :no_content, "")
340 end
341 end
342
343 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
344 with {:ok, _message} <- Relay.follow(target) do
345 ModerationLog.insert_log(%{
346 action: "relay_follow",
347 actor: admin,
348 target: target
349 })
350
351 json(conn, target)
352 else
353 _ ->
354 conn
355 |> put_status(500)
356 |> json(target)
357 end
358 end
359
360 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
361 with {:ok, _message} <- Relay.unfollow(target) do
362 ModerationLog.insert_log(%{
363 action: "relay_unfollow",
364 actor: admin,
365 target: target
366 })
367
368 json(conn, target)
369 else
370 _ ->
371 conn
372 |> put_status(500)
373 |> json(target)
374 end
375 end
376
377 @doc "Sends registration invite via email"
378 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
379 with true <-
380 Pleroma.Config.get([:instance, :invites_enabled]) &&
381 !Pleroma.Config.get([:instance, :registrations_open]),
382 {:ok, invite_token} <- UserInviteToken.create_invite(),
383 email <-
384 Pleroma.Emails.UserEmail.user_invitation_email(
385 user,
386 invite_token,
387 email,
388 params["name"]
389 ),
390 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
391 json_response(conn, :no_content, "")
392 end
393 end
394
395 @doc "Create an account registration invite token"
396 def create_invite_token(conn, params) do
397 opts = %{}
398
399 opts =
400 if params["max_use"],
401 do: Map.put(opts, :max_use, params["max_use"]),
402 else: opts
403
404 opts =
405 if params["expires_at"],
406 do: Map.put(opts, :expires_at, params["expires_at"]),
407 else: opts
408
409 {:ok, invite} = UserInviteToken.create_invite(opts)
410
411 json(conn, AccountView.render("invite.json", %{invite: invite}))
412 end
413
414 @doc "Get list of created invites"
415 def invites(conn, _params) do
416 invites = UserInviteToken.list_invites()
417
418 conn
419 |> put_view(AccountView)
420 |> render("invites.json", %{invites: invites})
421 end
422
423 @doc "Revokes invite by token"
424 def revoke_invite(conn, %{"token" => token}) do
425 with {:ok, invite} <- UserInviteToken.find_by_token(token),
426 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
427 conn
428 |> put_view(AccountView)
429 |> render("invite.json", %{invite: updated_invite})
430 else
431 nil -> {:error, :not_found}
432 end
433 end
434
435 @doc "Get a password reset token (base64 string) for given nickname"
436 def get_password_reset(conn, %{"nickname" => nickname}) do
437 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
438 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
439
440 conn
441 |> json(%{
442 token: token.token,
443 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
444 })
445 end
446
447 @doc "Force password reset for a given user"
448 def force_password_reset(conn, %{"nickname" => nickname}) do
449 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
450
451 User.force_password_reset_async(user)
452
453 json_response(conn, :no_content, "")
454 end
455
456 def list_reports(conn, params) do
457 {page, page_size} = page_params(params)
458
459 conn
460 |> put_view(ReportView)
461 |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)})
462 end
463
464 def list_grouped_reports(conn, _params) do
465 conn
466 |> put_view(ReportView)
467 |> render("index_grouped.json", Utils.get_reports_grouped_by_status())
468 end
469
470 def report_show(conn, %{"id" => id}) do
471 with %Activity{} = report <- Activity.get_by_id(id) do
472 conn
473 |> put_view(ReportView)
474 |> render("show.json", Report.extract_report_info(report))
475 else
476 _ -> {:error, :not_found}
477 end
478 end
479
480 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
481 result =
482 reports
483 |> Enum.map(fn report ->
484 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
485 ModerationLog.insert_log(%{
486 action: "report_update",
487 actor: admin,
488 subject: activity
489 })
490
491 activity
492 else
493 {:error, message} -> %{id: report["id"], error: message}
494 end
495 end)
496
497 case Enum.any?(result, &Map.has_key?(&1, :error)) do
498 true -> json_response(conn, :bad_request, result)
499 false -> json_response(conn, :no_content, "")
500 end
501 end
502
503 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
504 with false <- is_nil(params["status"]),
505 %Activity{} <- Activity.get_by_id(id) do
506 params =
507 params
508 |> Map.put("in_reply_to_status_id", id)
509 |> Map.put("visibility", "direct")
510
511 {:ok, activity} = CommonAPI.post(user, params)
512
513 ModerationLog.insert_log(%{
514 action: "report_response",
515 actor: user,
516 subject: activity,
517 text: params["status"]
518 })
519
520 conn
521 |> put_view(StatusView)
522 |> render("show.json", %{activity: activity})
523 else
524 true ->
525 {:param_cast, nil}
526
527 nil ->
528 {:error, :not_found}
529 end
530 end
531
532 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
533 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
534 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
535
536 ModerationLog.insert_log(%{
537 action: "status_update",
538 actor: admin,
539 subject: activity,
540 sensitive: sensitive,
541 visibility: params["visibility"]
542 })
543
544 conn
545 |> put_view(StatusView)
546 |> render("show.json", %{activity: activity})
547 end
548 end
549
550 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
551 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
552 ModerationLog.insert_log(%{
553 action: "status_delete",
554 actor: user,
555 subject_id: id
556 })
557
558 json(conn, %{})
559 end
560 end
561
562 def list_log(conn, params) do
563 {page, page_size} = page_params(params)
564
565 log =
566 ModerationLog.get_all(%{
567 page: page,
568 page_size: page_size,
569 start_date: params["start_date"],
570 end_date: params["end_date"],
571 user_id: params["user_id"],
572 search: params["search"]
573 })
574
575 conn
576 |> put_view(ModerationLogView)
577 |> render("index.json", %{log: log})
578 end
579
580 def migrate_to_db(conn, _params) do
581 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
582 json(conn, %{})
583 end
584
585 def migrate_from_db(conn, _params) do
586 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
587 json(conn, %{})
588 end
589
590 def config_show(conn, _params) do
591 configs = Pleroma.Repo.all(Config)
592
593 conn
594 |> put_view(ConfigView)
595 |> render("index.json", %{configs: configs})
596 end
597
598 def config_update(conn, %{"configs" => configs}) do
599 updated =
600 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
601 updated =
602 Enum.map(configs, fn
603 %{"group" => group, "key" => key, "delete" => "true"} = params ->
604 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
605 config
606
607 %{"group" => group, "key" => key, "value" => value} ->
608 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
609 config
610 end)
611 |> Enum.reject(&is_nil(&1))
612
613 Pleroma.Config.TransferTask.load_and_update_env()
614 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
615 updated
616 else
617 []
618 end
619
620 conn
621 |> put_view(ConfigView)
622 |> render("index.json", %{configs: updated})
623 end
624
625 def reload_emoji(conn, _params) do
626 Pleroma.Emoji.reload()
627
628 conn |> json("ok")
629 end
630
631 def errors(conn, {:error, :not_found}) do
632 conn
633 |> put_status(:not_found)
634 |> json(dgettext("errors", "Not found"))
635 end
636
637 def errors(conn, {:error, reason}) do
638 conn
639 |> put_status(:bad_request)
640 |> json(reason)
641 end
642
643 def errors(conn, {:param_cast, _}) do
644 conn
645 |> put_status(:bad_request)
646 |> json(dgettext("errors", "Invalid parameters"))
647 end
648
649 def errors(conn, _) do
650 conn
651 |> put_status(:internal_server_error)
652 |> json(dgettext("errors", "Something went wrong"))
653 end
654
655 defp page_params(params) do
656 {get_page(params["page"]), get_page_size(params["page_size"])}
657 end
658
659 defp get_page(page_string) when is_nil(page_string), do: 1
660
661 defp get_page(page_string) do
662 case Integer.parse(page_string) do
663 {page, _} -> page
664 :error -> 1
665 end
666 end
667
668 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
669
670 defp get_page_size(page_size_string) do
671 case Integer.parse(page_size_string) do
672 {page_size, _} -> page_size
673 :error -> @users_page_size
674 end
675 end
676 end