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