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