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