Merge branch 'remove-direct-messages-from-public-timeline' into 'develop'
[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 user = User.get_cached_by_nickname(nickname)
258
259 info =
260 %{}
261 |> Map.put("is_" <> permission_group, true)
262
263 info_cng = User.Info.admin_api_update(user.info, info)
264
265 cng =
266 user
267 |> Ecto.Changeset.change()
268 |> Ecto.Changeset.put_embed(:info, info_cng)
269
270 ModerationLog.insert_log(%{
271 action: "grant",
272 actor: admin,
273 subject: user,
274 permission: permission_group
275 })
276
277 {:ok, _user} = User.update_and_set_cache(cng)
278
279 json(conn, info)
280 end
281
282 def right_add(conn, _) do
283 render_error(conn, :not_found, "No such permission_group")
284 end
285
286 def right_get(conn, %{"nickname" => nickname}) do
287 user = User.get_cached_by_nickname(nickname)
288
289 conn
290 |> json(%{
291 is_moderator: user.info.is_moderator,
292 is_admin: user.info.is_admin
293 })
294 end
295
296 def right_delete(
297 %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
298 %{
299 "permission_group" => permission_group,
300 "nickname" => nickname
301 }
302 )
303 when permission_group in ["moderator", "admin"] do
304 if admin_nickname == nickname do
305 render_error(conn, :forbidden, "You can't revoke your own admin status.")
306 else
307 user = User.get_cached_by_nickname(nickname)
308
309 info =
310 %{}
311 |> Map.put("is_" <> permission_group, false)
312
313 info_cng = User.Info.admin_api_update(user.info, info)
314
315 cng =
316 Ecto.Changeset.change(user)
317 |> Ecto.Changeset.put_embed(:info, info_cng)
318
319 {:ok, _user} = User.update_and_set_cache(cng)
320
321 ModerationLog.insert_log(%{
322 action: "revoke",
323 actor: admin,
324 subject: user,
325 permission: permission_group
326 })
327
328 json(conn, info)
329 end
330 end
331
332 def right_delete(conn, _) do
333 render_error(conn, :not_found, "No such permission_group")
334 end
335
336 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
337 "nickname" => nickname,
338 "status" => status
339 }) do
340 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
341 %User{} = user <- User.get_cached_by_nickname(nickname),
342 {:ok, _} <- User.deactivate(user, !status) do
343 action = if(user.info.deactivated, do: "activate", else: "deactivate")
344
345 ModerationLog.insert_log(%{
346 actor: admin,
347 subject: user,
348 action: action
349 })
350
351 json_response(conn, :no_content, "")
352 end
353 end
354
355 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
356 with {:ok, _message} <- Relay.follow(target) do
357 ModerationLog.insert_log(%{
358 action: "relay_follow",
359 actor: admin,
360 target: target
361 })
362
363 json(conn, target)
364 else
365 _ ->
366 conn
367 |> put_status(500)
368 |> json(target)
369 end
370 end
371
372 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
373 with {:ok, _message} <- Relay.unfollow(target) do
374 ModerationLog.insert_log(%{
375 action: "relay_unfollow",
376 actor: admin,
377 target: target
378 })
379
380 json(conn, target)
381 else
382 _ ->
383 conn
384 |> put_status(500)
385 |> json(target)
386 end
387 end
388
389 @doc "Sends registration invite via email"
390 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
391 with true <-
392 Pleroma.Config.get([:instance, :invites_enabled]) &&
393 !Pleroma.Config.get([:instance, :registrations_open]),
394 {:ok, invite_token} <- UserInviteToken.create_invite(),
395 email <-
396 Pleroma.Emails.UserEmail.user_invitation_email(
397 user,
398 invite_token,
399 email,
400 params["name"]
401 ),
402 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
403 json_response(conn, :no_content, "")
404 end
405 end
406
407 @doc "Create an account registration invite token"
408 def create_invite_token(conn, params) do
409 opts = %{}
410
411 opts =
412 if params["max_use"],
413 do: Map.put(opts, :max_use, params["max_use"]),
414 else: opts
415
416 opts =
417 if params["expires_at"],
418 do: Map.put(opts, :expires_at, params["expires_at"]),
419 else: opts
420
421 {:ok, invite} = UserInviteToken.create_invite(opts)
422
423 json(conn, AccountView.render("invite.json", %{invite: invite}))
424 end
425
426 @doc "Get list of created invites"
427 def invites(conn, _params) do
428 invites = UserInviteToken.list_invites()
429
430 conn
431 |> put_view(AccountView)
432 |> render("invites.json", %{invites: invites})
433 end
434
435 @doc "Revokes invite by token"
436 def revoke_invite(conn, %{"token" => token}) do
437 with {:ok, invite} <- UserInviteToken.find_by_token(token),
438 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
439 conn
440 |> put_view(AccountView)
441 |> render("invite.json", %{invite: updated_invite})
442 else
443 nil -> {:error, :not_found}
444 end
445 end
446
447 @doc "Get a password reset token (base64 string) for given nickname"
448 def get_password_reset(conn, %{"nickname" => nickname}) do
449 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
450 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
451
452 conn
453 |> json(token.token)
454 end
455
456 def list_reports(conn, params) do
457 params =
458 params
459 |> Map.put("type", "Flag")
460 |> Map.put("skip_preload", true)
461 |> Map.put("total", true)
462
463 reports = ActivityPub.fetch_activities([], params)
464
465 conn
466 |> put_view(ReportView)
467 |> render("index.json", %{reports: reports})
468 end
469
470 def report_show(conn, %{"id" => id}) do
471 with %Activity{} = report <- Activity.get_by_id(id) do
472 conn
473 |> put_view(ReportView)
474 |> render("show.json", Report.extract_report_info(report))
475 else
476 _ -> {:error, :not_found}
477 end
478 end
479
480 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
481 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
482 ModerationLog.insert_log(%{
483 action: "report_update",
484 actor: admin,
485 subject: report
486 })
487
488 conn
489 |> put_view(ReportView)
490 |> render("show.json", Report.extract_report_info(report))
491 end
492 end
493
494 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
495 with false <- is_nil(params["status"]),
496 %Activity{} <- Activity.get_by_id(id) do
497 params =
498 params
499 |> Map.put("in_reply_to_status_id", id)
500 |> Map.put("visibility", "direct")
501
502 {:ok, activity} = CommonAPI.post(user, params)
503
504 ModerationLog.insert_log(%{
505 action: "report_response",
506 actor: user,
507 subject: activity,
508 text: params["status"]
509 })
510
511 conn
512 |> put_view(StatusView)
513 |> render("status.json", %{activity: activity})
514 else
515 true ->
516 {:param_cast, nil}
517
518 nil ->
519 {:error, :not_found}
520 end
521 end
522
523 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
524 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
525 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
526
527 ModerationLog.insert_log(%{
528 action: "status_update",
529 actor: admin,
530 subject: activity,
531 sensitive: sensitive,
532 visibility: params["visibility"]
533 })
534
535 conn
536 |> put_view(StatusView)
537 |> render("status.json", %{activity: activity})
538 end
539 end
540
541 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
542 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
543 ModerationLog.insert_log(%{
544 action: "status_delete",
545 actor: user,
546 subject_id: id
547 })
548
549 json(conn, %{})
550 end
551 end
552
553 def list_log(conn, params) do
554 {page, page_size} = page_params(params)
555
556 log = ModerationLog.get_all(page, page_size)
557
558 conn
559 |> put_view(ModerationLogView)
560 |> render("index.json", %{log: log})
561 end
562
563 def migrate_to_db(conn, _params) do
564 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
565 json(conn, %{})
566 end
567
568 def migrate_from_db(conn, _params) do
569 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
570 json(conn, %{})
571 end
572
573 def config_show(conn, _params) do
574 configs = Pleroma.Repo.all(Config)
575
576 conn
577 |> put_view(ConfigView)
578 |> render("index.json", %{configs: configs})
579 end
580
581 def config_update(conn, %{"configs" => configs}) do
582 updated =
583 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
584 updated =
585 Enum.map(configs, fn
586 %{"group" => group, "key" => key, "delete" => "true"} = params ->
587 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
588 config
589
590 %{"group" => group, "key" => key, "value" => value} ->
591 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
592 config
593 end)
594 |> Enum.reject(&is_nil(&1))
595
596 Pleroma.Config.TransferTask.load_and_update_env()
597 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
598 updated
599 else
600 []
601 end
602
603 conn
604 |> put_view(ConfigView)
605 |> render("index.json", %{configs: updated})
606 end
607
608 def reload_emoji(conn, _params) do
609 Pleroma.Emoji.reload()
610
611 conn |> json("ok")
612 end
613
614 def errors(conn, {:error, :not_found}) do
615 conn
616 |> put_status(:not_found)
617 |> json(dgettext("errors", "Not found"))
618 end
619
620 def errors(conn, {:error, reason}) do
621 conn
622 |> put_status(:bad_request)
623 |> json(reason)
624 end
625
626 def errors(conn, {:param_cast, _}) do
627 conn
628 |> put_status(:bad_request)
629 |> json(dgettext("errors", "Invalid parameters"))
630 end
631
632 def errors(conn, _) do
633 conn
634 |> put_status(:internal_server_error)
635 |> json(dgettext("errors", "Something went wrong"))
636 end
637
638 defp page_params(params) do
639 {get_page(params["page"]), get_page_size(params["page_size"])}
640 end
641
642 defp get_page(page_string) when is_nil(page_string), do: 1
643
644 defp get_page(page_string) do
645 case Integer.parse(page_string) do
646 {page, _} -> page
647 :error -> 1
648 end
649 end
650
651 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
652
653 defp get_page_size(page_size_string) do
654 case Integer.parse(page_size_string) do
655 {page_size, _} -> page_size
656 :error -> @users_page_size
657 end
658 end
659 end