460188390eb87835d8ff9a11f643760cdc56ea07
[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 "Create an account registration invite token"
406 def create_invite_token(conn, params) do
407 opts = %{}
408
409 opts =
410 if params["max_use"],
411 do: Map.put(opts, :max_use, params["max_use"]),
412 else: opts
413
414 opts =
415 if params["expires_at"],
416 do: Map.put(opts, :expires_at, params["expires_at"]),
417 else: opts
418
419 {:ok, invite} = UserInviteToken.create_invite(opts)
420
421 json(conn, AccountView.render("invite.json", %{invite: invite}))
422 end
423
424 @doc "Get list of created invites"
425 def invites(conn, _params) do
426 invites = UserInviteToken.list_invites()
427
428 conn
429 |> json(AccountView.render("invites.json", %{invites: invites}))
430 end
431
432 @doc "Revokes invite by token"
433 def revoke_invite(conn, %{"token" => token}) do
434 with {:ok, invite} <- UserInviteToken.find_by_token(token),
435 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
436 conn
437 |> json(AccountView.render("invite.json", %{invite: updated_invite}))
438 else
439 nil -> {:error, :not_found}
440 end
441 end
442
443 @doc "Get a password reset token (base64 string) for given nickname"
444 def get_password_reset(conn, %{"nickname" => nickname}) do
445 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
446 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
447
448 conn
449 |> json(%{
450 token: token.token,
451 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
452 })
453 end
454
455 def list_reports(conn, params) do
456 params =
457 params
458 |> Map.put("type", "Flag")
459 |> Map.put("skip_preload", true)
460 |> Map.put("total", true)
461
462 reports = ActivityPub.fetch_activities([], params)
463
464 conn
465 |> put_view(ReportView)
466 |> render("index.json", %{reports: reports})
467 end
468
469 def report_show(conn, %{"id" => id}) do
470 with %Activity{} = report <- Activity.get_by_id(id) do
471 conn
472 |> put_view(ReportView)
473 |> render("show.json", %{report: report})
474 else
475 _ -> {:error, :not_found}
476 end
477 end
478
479 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
480 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
481 ModerationLog.insert_log(%{
482 action: "report_update",
483 actor: admin,
484 subject: report
485 })
486
487 conn
488 |> put_view(ReportView)
489 |> render("show.json", %{report: report})
490 end
491 end
492
493 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
494 with false <- is_nil(params["status"]),
495 %Activity{} <- Activity.get_by_id(id) do
496 params =
497 params
498 |> Map.put("in_reply_to_status_id", id)
499 |> Map.put("visibility", "direct")
500
501 {:ok, activity} = CommonAPI.post(user, params)
502
503 ModerationLog.insert_log(%{
504 action: "report_response",
505 actor: user,
506 subject: activity,
507 text: params["status"]
508 })
509
510 conn
511 |> put_view(StatusView)
512 |> render("status.json", %{activity: activity})
513 else
514 true ->
515 {:param_cast, nil}
516
517 nil ->
518 {:error, :not_found}
519 end
520 end
521
522 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
523 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
524 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
525
526 ModerationLog.insert_log(%{
527 action: "status_update",
528 actor: admin,
529 subject: activity,
530 sensitive: sensitive,
531 visibility: params["visibility"]
532 })
533
534 conn
535 |> put_view(StatusView)
536 |> render("status.json", %{activity: activity})
537 end
538 end
539
540 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
541 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
542 ModerationLog.insert_log(%{
543 action: "status_delete",
544 actor: user,
545 subject_id: id
546 })
547
548 json(conn, %{})
549 end
550 end
551
552 def list_log(conn, params) do
553 {page, page_size} = page_params(params)
554
555 log = ModerationLog.get_all(page, page_size)
556
557 conn
558 |> put_view(ModerationLogView)
559 |> render("index.json", %{log: log})
560 end
561
562 def migrate_to_db(conn, _params) do
563 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
564 json(conn, %{})
565 end
566
567 def migrate_from_db(conn, _params) do
568 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
569 json(conn, %{})
570 end
571
572 def config_show(conn, _params) do
573 configs = Pleroma.Repo.all(Config)
574
575 conn
576 |> put_view(ConfigView)
577 |> render("index.json", %{configs: configs})
578 end
579
580 def config_update(conn, %{"configs" => configs}) do
581 updated =
582 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
583 updated =
584 Enum.map(configs, fn
585 %{"group" => group, "key" => key, "delete" => "true"} = params ->
586 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
587 config
588
589 %{"group" => group, "key" => key, "value" => value} ->
590 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
591 config
592 end)
593 |> Enum.reject(&is_nil(&1))
594
595 Pleroma.Config.TransferTask.load_and_update_env()
596 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
597 updated
598 else
599 []
600 end
601
602 conn
603 |> put_view(ConfigView)
604 |> render("index.json", %{configs: updated})
605 end
606
607 def errors(conn, {:error, :not_found}) do
608 conn
609 |> put_status(:not_found)
610 |> json(dgettext("errors", "Not found"))
611 end
612
613 def errors(conn, {:error, reason}) do
614 conn
615 |> put_status(:bad_request)
616 |> json(reason)
617 end
618
619 def errors(conn, {:param_cast, _}) do
620 conn
621 |> put_status(:bad_request)
622 |> json(dgettext("errors", "Invalid parameters"))
623 end
624
625 def errors(conn, _) do
626 conn
627 |> put_status(:internal_server_error)
628 |> json(dgettext("errors", "Something went wrong"))
629 end
630
631 defp page_params(params) do
632 {get_page(params["page"]), get_page_size(params["page_size"])}
633 end
634
635 defp get_page(page_string) when is_nil(page_string), do: 1
636
637 defp get_page(page_string) do
638 case Integer.parse(page_string) do
639 {page, _} -> page
640 :error -> 1
641 end
642 end
643
644 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
645
646 defp get_page_size(page_size_string) do
647 case Integer.parse(page_size_string) do
648 {page_size, _} -> page_size
649 :error -> @users_page_size
650 end
651 end
652 end