Merge branch 'better-emoji-packs' 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.ReportView
18 alias Pleroma.Web.AdminAPI.Search
19 alias Pleroma.Web.CommonAPI
20 alias Pleroma.Web.MastodonAPI.StatusView
21
22 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
23
24 require Logger
25
26 @users_page_size 50
27
28 action_fallback(:errors)
29
30 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
31 user = User.get_cached_by_nickname(nickname)
32 User.delete(user)
33
34 ModerationLog.insert_log(%{
35 actor: admin,
36 subject: user,
37 action: "delete"
38 })
39
40 conn
41 |> json(nickname)
42 end
43
44 def user_follow(%{assigns: %{user: admin}} = conn, %{
45 "follower" => follower_nick,
46 "followed" => followed_nick
47 }) do
48 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
49 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
50 User.follow(follower, followed)
51
52 ModerationLog.insert_log(%{
53 actor: admin,
54 followed: followed,
55 follower: follower,
56 action: "follow"
57 })
58 end
59
60 conn
61 |> json("ok")
62 end
63
64 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
65 "follower" => follower_nick,
66 "followed" => followed_nick
67 }) do
68 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
69 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
70 User.unfollow(follower, followed)
71
72 ModerationLog.insert_log(%{
73 actor: admin,
74 followed: followed,
75 follower: follower,
76 action: "unfollow"
77 })
78 end
79
80 conn
81 |> json("ok")
82 end
83
84 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
85 changesets =
86 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
87 user_data = %{
88 nickname: nickname,
89 name: nickname,
90 email: email,
91 password: password,
92 password_confirmation: password,
93 bio: "."
94 }
95
96 User.register_changeset(%User{}, user_data, need_confirmation: false)
97 end)
98 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
99 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
100 end)
101
102 case Pleroma.Repo.transaction(changesets) do
103 {:ok, users} ->
104 res =
105 users
106 |> Map.values()
107 |> Enum.map(fn user ->
108 {:ok, user} = User.post_register_action(user)
109
110 user
111 end)
112 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
113
114 ModerationLog.insert_log(%{
115 actor: admin,
116 subjects: Map.values(users),
117 action: "create"
118 })
119
120 conn
121 |> json(res)
122
123 {:error, id, changeset, _} ->
124 res =
125 Enum.map(changesets.operations, fn
126 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
127 AccountView.render("create-error.json", %{changeset: changeset})
128
129 {_, {:changeset, current_changeset, _}} ->
130 AccountView.render("create-error.json", %{changeset: current_changeset})
131 end)
132
133 conn
134 |> put_status(:conflict)
135 |> json(res)
136 end
137 end
138
139 def user_show(conn, %{"nickname" => nickname}) do
140 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
141 conn
142 |> json(AccountView.render("show.json", %{user: user}))
143 else
144 _ -> {:error, :not_found}
145 end
146 end
147
148 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
149 godmode = params["godmode"] == "true" || params["godmode"] == true
150
151 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
152 {_, page_size} = page_params(params)
153
154 activities =
155 ActivityPub.fetch_user_activities(user, nil, %{
156 "limit" => page_size,
157 "godmode" => godmode
158 })
159
160 conn
161 |> json(StatusView.render("index.json", %{activities: activities, as: :activity}))
162 else
163 _ -> {:error, :not_found}
164 end
165 end
166
167 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
168 user = User.get_cached_by_nickname(nickname)
169
170 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
171
172 action = if user.info.deactivated, do: "activate", else: "deactivate"
173
174 ModerationLog.insert_log(%{
175 actor: admin,
176 subject: user,
177 action: action
178 })
179
180 conn
181 |> json(AccountView.render("show.json", %{user: updated_user}))
182 end
183
184 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
185 with {:ok, _} <- User.tag(nicknames, tags) do
186 ModerationLog.insert_log(%{
187 actor: admin,
188 nicknames: nicknames,
189 tags: tags,
190 action: "tag"
191 })
192
193 json_response(conn, :no_content, "")
194 end
195 end
196
197 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
198 with {:ok, _} <- User.untag(nicknames, tags) do
199 ModerationLog.insert_log(%{
200 actor: admin,
201 nicknames: nicknames,
202 tags: tags,
203 action: "untag"
204 })
205
206 json_response(conn, :no_content, "")
207 end
208 end
209
210 def list_users(conn, params) do
211 {page, page_size} = page_params(params)
212 filters = maybe_parse_filters(params["filters"])
213
214 search_params = %{
215 query: params["query"],
216 page: page,
217 page_size: page_size,
218 tags: params["tags"],
219 name: params["name"],
220 email: params["email"]
221 }
222
223 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
224 do:
225 conn
226 |> json(
227 AccountView.render("index.json",
228 users: users,
229 count: count,
230 page_size: page_size
231 )
232 )
233 end
234
235 @filters ~w(local external active deactivated is_admin is_moderator)
236
237 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
238 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
239
240 defp maybe_parse_filters(filters) do
241 filters
242 |> String.split(",")
243 |> Enum.filter(&Enum.member?(@filters, &1))
244 |> Enum.map(&String.to_atom(&1))
245 |> Enum.into(%{}, &{&1, true})
246 end
247
248 def right_add(%{assigns: %{user: admin}} = conn, %{
249 "permission_group" => permission_group,
250 "nickname" => nickname
251 })
252 when permission_group in ["moderator", "admin"] do
253 user = User.get_cached_by_nickname(nickname)
254
255 info =
256 %{}
257 |> Map.put("is_" <> permission_group, true)
258
259 info_cng = User.Info.admin_api_update(user.info, info)
260
261 cng =
262 user
263 |> Ecto.Changeset.change()
264 |> Ecto.Changeset.put_embed(:info, info_cng)
265
266 ModerationLog.insert_log(%{
267 action: "grant",
268 actor: admin,
269 subject: user,
270 permission: permission_group
271 })
272
273 {:ok, _user} = User.update_and_set_cache(cng)
274
275 json(conn, info)
276 end
277
278 def right_add(conn, _) do
279 render_error(conn, :not_found, "No such permission_group")
280 end
281
282 def right_get(conn, %{"nickname" => nickname}) do
283 user = User.get_cached_by_nickname(nickname)
284
285 conn
286 |> json(%{
287 is_moderator: user.info.is_moderator,
288 is_admin: user.info.is_admin
289 })
290 end
291
292 def right_delete(
293 %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
294 %{
295 "permission_group" => permission_group,
296 "nickname" => nickname
297 }
298 )
299 when permission_group in ["moderator", "admin"] do
300 if admin_nickname == nickname do
301 render_error(conn, :forbidden, "You can't revoke your own admin status.")
302 else
303 user = User.get_cached_by_nickname(nickname)
304
305 info =
306 %{}
307 |> Map.put("is_" <> permission_group, false)
308
309 info_cng = User.Info.admin_api_update(user.info, info)
310
311 cng =
312 Ecto.Changeset.change(user)
313 |> Ecto.Changeset.put_embed(:info, info_cng)
314
315 {:ok, _user} = User.update_and_set_cache(cng)
316
317 ModerationLog.insert_log(%{
318 action: "revoke",
319 actor: admin,
320 subject: user,
321 permission: permission_group
322 })
323
324 json(conn, info)
325 end
326 end
327
328 def right_delete(conn, _) do
329 render_error(conn, :not_found, "No such permission_group")
330 end
331
332 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
333 "nickname" => nickname,
334 "status" => status
335 }) do
336 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
337 %User{} = user <- User.get_cached_by_nickname(nickname),
338 {:ok, _} <- User.deactivate(user, !status) do
339 action = if(user.info.deactivated, do: "activate", else: "deactivate")
340
341 ModerationLog.insert_log(%{
342 actor: admin,
343 subject: user,
344 action: action
345 })
346
347 json_response(conn, :no_content, "")
348 end
349 end
350
351 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
352 with {:ok, _message} <- Relay.follow(target) do
353 ModerationLog.insert_log(%{
354 action: "relay_follow",
355 actor: admin,
356 target: target
357 })
358
359 json(conn, target)
360 else
361 _ ->
362 conn
363 |> put_status(500)
364 |> json(target)
365 end
366 end
367
368 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
369 with {:ok, _message} <- Relay.unfollow(target) do
370 ModerationLog.insert_log(%{
371 action: "relay_unfollow",
372 actor: admin,
373 target: target
374 })
375
376 json(conn, target)
377 else
378 _ ->
379 conn
380 |> put_status(500)
381 |> json(target)
382 end
383 end
384
385 @doc "Sends registration invite via email"
386 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
387 with true <-
388 Pleroma.Config.get([:instance, :invites_enabled]) &&
389 !Pleroma.Config.get([:instance, :registrations_open]),
390 {:ok, invite_token} <- UserInviteToken.create_invite(),
391 email <-
392 Pleroma.Emails.UserEmail.user_invitation_email(
393 user,
394 invite_token,
395 email,
396 params["name"]
397 ),
398 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
399 json_response(conn, :no_content, "")
400 end
401 end
402
403 @doc "Create an account registration invite token"
404 def create_invite_token(conn, params) do
405 opts = %{}
406
407 opts =
408 if params["max_use"],
409 do: Map.put(opts, :max_use, params["max_use"]),
410 else: opts
411
412 opts =
413 if params["expires_at"],
414 do: Map.put(opts, :expires_at, params["expires_at"]),
415 else: opts
416
417 {:ok, invite} = UserInviteToken.create_invite(opts)
418
419 json(conn, AccountView.render("invite.json", %{invite: invite}))
420 end
421
422 @doc "Get list of created invites"
423 def invites(conn, _params) do
424 invites = UserInviteToken.list_invites()
425
426 conn
427 |> json(AccountView.render("invites.json", %{invites: invites}))
428 end
429
430 @doc "Revokes invite by token"
431 def revoke_invite(conn, %{"token" => token}) do
432 with {:ok, invite} <- UserInviteToken.find_by_token(token),
433 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
434 conn
435 |> json(AccountView.render("invite.json", %{invite: updated_invite}))
436 else
437 nil -> {:error, :not_found}
438 end
439 end
440
441 @doc "Get a password reset token (base64 string) for given nickname"
442 def get_password_reset(conn, %{"nickname" => nickname}) do
443 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
444 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
445
446 conn
447 |> json(token.token)
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: 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: 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