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