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