Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

706 lines
19KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.AdminAPI.AdminAPIController do
  5. use Pleroma.Web, :controller
  6. import Pleroma.Web.ControllerHelper, only: [json_response: 3]
  7. alias Pleroma.Config
  8. alias Pleroma.MFA
  9. alias Pleroma.ModerationLog
  10. alias Pleroma.Stats
  11. alias Pleroma.User
  12. alias Pleroma.Web.ActivityPub.ActivityPub
  13. alias Pleroma.Web.ActivityPub.Builder
  14. alias Pleroma.Web.ActivityPub.Pipeline
  15. alias Pleroma.Web.AdminAPI
  16. alias Pleroma.Web.AdminAPI.AccountView
  17. alias Pleroma.Web.AdminAPI.ModerationLogView
  18. alias Pleroma.Web.AdminAPI.Search
  19. alias Pleroma.Web.Endpoint
  20. alias Pleroma.Web.Plugs.OAuthScopesPlug
  21. alias Pleroma.Web.Router
  22. @users_page_size 50
  23. plug(
  24. OAuthScopesPlug,
  25. %{scopes: ["read:accounts"], admin: true}
  26. when action in [:list_users, :user_show, :right_get, :show_user_credentials]
  27. )
  28. plug(
  29. OAuthScopesPlug,
  30. %{scopes: ["write:accounts"], admin: true}
  31. when action in [
  32. :get_password_reset,
  33. :force_password_reset,
  34. :user_delete,
  35. :users_create,
  36. :user_toggle_activation,
  37. :user_activate,
  38. :user_deactivate,
  39. :user_approve,
  40. :tag_users,
  41. :untag_users,
  42. :right_add,
  43. :right_add_multiple,
  44. :right_delete,
  45. :disable_mfa,
  46. :right_delete_multiple,
  47. :update_user_credentials
  48. ]
  49. )
  50. plug(
  51. OAuthScopesPlug,
  52. %{scopes: ["write:follows"], admin: true}
  53. when action in [:user_follow, :user_unfollow]
  54. )
  55. plug(
  56. OAuthScopesPlug,
  57. %{scopes: ["read:statuses"], admin: true}
  58. when action in [:list_user_statuses, :list_instance_statuses]
  59. )
  60. plug(
  61. OAuthScopesPlug,
  62. %{scopes: ["read:chats"], admin: true}
  63. when action in [:list_user_chats]
  64. )
  65. plug(
  66. OAuthScopesPlug,
  67. %{scopes: ["read"], admin: true}
  68. when action in [
  69. :list_log,
  70. :stats,
  71. :need_reboot
  72. ]
  73. )
  74. plug(
  75. OAuthScopesPlug,
  76. %{scopes: ["write"], admin: true}
  77. when action in [
  78. :restart,
  79. :resend_confirmation_email,
  80. :confirm_email,
  81. :reload_emoji
  82. ]
  83. )
  84. action_fallback(AdminAPI.FallbackController)
  85. def user_delete(conn, %{"nickname" => nickname}) do
  86. user_delete(conn, %{"nicknames" => [nickname]})
  87. end
  88. def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
  89. users =
  90. nicknames
  91. |> Enum.map(&User.get_cached_by_nickname/1)
  92. users
  93. |> Enum.each(fn user ->
  94. {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
  95. Pipeline.common_pipeline(delete_data, local: true)
  96. end)
  97. ModerationLog.insert_log(%{
  98. actor: admin,
  99. subject: users,
  100. action: "delete"
  101. })
  102. json(conn, nicknames)
  103. end
  104. def user_follow(%{assigns: %{user: admin}} = conn, %{
  105. "follower" => follower_nick,
  106. "followed" => followed_nick
  107. }) do
  108. with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
  109. %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
  110. User.follow(follower, followed)
  111. ModerationLog.insert_log(%{
  112. actor: admin,
  113. followed: followed,
  114. follower: follower,
  115. action: "follow"
  116. })
  117. end
  118. json(conn, "ok")
  119. end
  120. def user_unfollow(%{assigns: %{user: admin}} = conn, %{
  121. "follower" => follower_nick,
  122. "followed" => followed_nick
  123. }) do
  124. with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
  125. %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
  126. User.unfollow(follower, followed)
  127. ModerationLog.insert_log(%{
  128. actor: admin,
  129. followed: followed,
  130. follower: follower,
  131. action: "unfollow"
  132. })
  133. end
  134. json(conn, "ok")
  135. end
  136. def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
  137. changesets =
  138. Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
  139. user_data = %{
  140. nickname: nickname,
  141. name: nickname,
  142. email: email,
  143. password: password,
  144. password_confirmation: password,
  145. bio: "."
  146. }
  147. User.register_changeset(%User{}, user_data, need_confirmation: false)
  148. end)
  149. |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
  150. Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
  151. end)
  152. case Pleroma.Repo.transaction(changesets) do
  153. {:ok, users} ->
  154. res =
  155. users
  156. |> Map.values()
  157. |> Enum.map(fn user ->
  158. {:ok, user} = User.post_register_action(user)
  159. user
  160. end)
  161. |> Enum.map(&AccountView.render("created.json", %{user: &1}))
  162. ModerationLog.insert_log(%{
  163. actor: admin,
  164. subjects: Map.values(users),
  165. action: "create"
  166. })
  167. json(conn, res)
  168. {:error, id, changeset, _} ->
  169. res =
  170. Enum.map(changesets.operations, fn
  171. {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
  172. AccountView.render("create-error.json", %{changeset: changeset})
  173. {_, {:changeset, current_changeset, _}} ->
  174. AccountView.render("create-error.json", %{changeset: current_changeset})
  175. end)
  176. conn
  177. |> put_status(:conflict)
  178. |> json(res)
  179. end
  180. end
  181. def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
  182. with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
  183. conn
  184. |> put_view(AccountView)
  185. |> render("show.json", %{user: user})
  186. else
  187. _ -> {:error, :not_found}
  188. end
  189. end
  190. def list_instance_statuses(conn, %{"instance" => instance} = params) do
  191. with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
  192. {page, page_size} = page_params(params)
  193. activities =
  194. ActivityPub.fetch_statuses(nil, %{
  195. instance: instance,
  196. limit: page_size,
  197. offset: (page - 1) * page_size,
  198. exclude_reblogs: not with_reblogs
  199. })
  200. conn
  201. |> put_view(AdminAPI.StatusView)
  202. |> render("index.json", %{activities: activities, as: :activity})
  203. end
  204. def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
  205. with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
  206. godmode = params["godmode"] == "true" || params["godmode"] == true
  207. with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
  208. {_, page_size} = page_params(params)
  209. activities =
  210. ActivityPub.fetch_user_activities(user, nil, %{
  211. limit: page_size,
  212. godmode: godmode,
  213. exclude_reblogs: not with_reblogs
  214. })
  215. conn
  216. |> put_view(AdminAPI.StatusView)
  217. |> render("index.json", %{activities: activities, as: :activity})
  218. else
  219. _ -> {:error, :not_found}
  220. end
  221. end
  222. def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
  223. with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
  224. chats =
  225. Pleroma.Chat.for_user_query(user_id)
  226. |> Pleroma.Repo.all()
  227. conn
  228. |> put_view(AdminAPI.ChatView)
  229. |> render("index.json", chats: chats)
  230. else
  231. _ -> {:error, :not_found}
  232. end
  233. end
  234. def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
  235. user = User.get_cached_by_nickname(nickname)
  236. {:ok, updated_user} = User.deactivate(user, !user.deactivated)
  237. action = if user.deactivated, do: "activate", else: "deactivate"
  238. ModerationLog.insert_log(%{
  239. actor: admin,
  240. subject: [user],
  241. action: action
  242. })
  243. conn
  244. |> put_view(AccountView)
  245. |> render("show.json", %{user: updated_user})
  246. end
  247. def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
  248. users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
  249. {:ok, updated_users} = User.deactivate(users, false)
  250. ModerationLog.insert_log(%{
  251. actor: admin,
  252. subject: users,
  253. action: "activate"
  254. })
  255. conn
  256. |> put_view(AccountView)
  257. |> render("index.json", %{users: Keyword.values(updated_users)})
  258. end
  259. def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
  260. users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
  261. {:ok, updated_users} = User.deactivate(users, true)
  262. ModerationLog.insert_log(%{
  263. actor: admin,
  264. subject: users,
  265. action: "deactivate"
  266. })
  267. conn
  268. |> put_view(AccountView)
  269. |> render("index.json", %{users: Keyword.values(updated_users)})
  270. end
  271. def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
  272. users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
  273. {:ok, updated_users} = User.approve(users)
  274. ModerationLog.insert_log(%{
  275. actor: admin,
  276. subject: users,
  277. action: "approve"
  278. })
  279. conn
  280. |> put_view(AccountView)
  281. |> render("index.json", %{users: updated_users})
  282. end
  283. def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
  284. with {:ok, _} <- User.tag(nicknames, tags) do
  285. ModerationLog.insert_log(%{
  286. actor: admin,
  287. nicknames: nicknames,
  288. tags: tags,
  289. action: "tag"
  290. })
  291. json_response(conn, :no_content, "")
  292. end
  293. end
  294. def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
  295. with {:ok, _} <- User.untag(nicknames, tags) do
  296. ModerationLog.insert_log(%{
  297. actor: admin,
  298. nicknames: nicknames,
  299. tags: tags,
  300. action: "untag"
  301. })
  302. json_response(conn, :no_content, "")
  303. end
  304. end
  305. def list_users(conn, params) do
  306. {page, page_size} = page_params(params)
  307. filters = maybe_parse_filters(params["filters"])
  308. search_params = %{
  309. query: params["query"],
  310. page: page,
  311. page_size: page_size,
  312. tags: params["tags"],
  313. name: params["name"],
  314. email: params["email"]
  315. }
  316. with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
  317. json(
  318. conn,
  319. AccountView.render("index.json",
  320. users: users,
  321. count: count,
  322. page_size: page_size
  323. )
  324. )
  325. end
  326. end
  327. @filters ~w(local external active deactivated need_approval is_admin is_moderator)
  328. @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
  329. defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
  330. defp maybe_parse_filters(filters) do
  331. filters
  332. |> String.split(",")
  333. |> Enum.filter(&Enum.member?(@filters, &1))
  334. |> Map.new(&{String.to_existing_atom(&1), true})
  335. end
  336. def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
  337. "permission_group" => permission_group,
  338. "nicknames" => nicknames
  339. })
  340. when permission_group in ["moderator", "admin"] do
  341. update = %{:"is_#{permission_group}" => true}
  342. users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
  343. for u <- users, do: User.admin_api_update(u, update)
  344. ModerationLog.insert_log(%{
  345. action: "grant",
  346. actor: admin,
  347. subject: users,
  348. permission: permission_group
  349. })
  350. json(conn, update)
  351. end
  352. def right_add_multiple(conn, _) do
  353. render_error(conn, :not_found, "No such permission_group")
  354. end
  355. def right_add(%{assigns: %{user: admin}} = conn, %{
  356. "permission_group" => permission_group,
  357. "nickname" => nickname
  358. })
  359. when permission_group in ["moderator", "admin"] do
  360. fields = %{:"is_#{permission_group}" => true}
  361. {:ok, user} =
  362. nickname
  363. |> User.get_cached_by_nickname()
  364. |> User.admin_api_update(fields)
  365. ModerationLog.insert_log(%{
  366. action: "grant",
  367. actor: admin,
  368. subject: [user],
  369. permission: permission_group
  370. })
  371. json(conn, fields)
  372. end
  373. def right_add(conn, _) do
  374. render_error(conn, :not_found, "No such permission_group")
  375. end
  376. def right_get(conn, %{"nickname" => nickname}) do
  377. user = User.get_cached_by_nickname(nickname)
  378. conn
  379. |> json(%{
  380. is_moderator: user.is_moderator,
  381. is_admin: user.is_admin
  382. })
  383. end
  384. def right_delete_multiple(
  385. %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
  386. %{
  387. "permission_group" => permission_group,
  388. "nicknames" => nicknames
  389. }
  390. )
  391. when permission_group in ["moderator", "admin"] do
  392. with false <- Enum.member?(nicknames, admin_nickname) do
  393. update = %{:"is_#{permission_group}" => false}
  394. users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
  395. for u <- users, do: User.admin_api_update(u, update)
  396. ModerationLog.insert_log(%{
  397. action: "revoke",
  398. actor: admin,
  399. subject: users,
  400. permission: permission_group
  401. })
  402. json(conn, update)
  403. else
  404. _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
  405. end
  406. end
  407. def right_delete_multiple(conn, _) do
  408. render_error(conn, :not_found, "No such permission_group")
  409. end
  410. def right_delete(
  411. %{assigns: %{user: admin}} = conn,
  412. %{
  413. "permission_group" => permission_group,
  414. "nickname" => nickname
  415. }
  416. )
  417. when permission_group in ["moderator", "admin"] do
  418. fields = %{:"is_#{permission_group}" => false}
  419. {:ok, user} =
  420. nickname
  421. |> User.get_cached_by_nickname()
  422. |> User.admin_api_update(fields)
  423. ModerationLog.insert_log(%{
  424. action: "revoke",
  425. actor: admin,
  426. subject: [user],
  427. permission: permission_group
  428. })
  429. json(conn, fields)
  430. end
  431. def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
  432. render_error(conn, :forbidden, "You can't revoke your own admin status.")
  433. end
  434. @doc "Get a password reset token (base64 string) for given nickname"
  435. def get_password_reset(conn, %{"nickname" => nickname}) do
  436. (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
  437. {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
  438. conn
  439. |> json(%{
  440. token: token.token,
  441. link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
  442. })
  443. end
  444. @doc "Force password reset for a given user"
  445. def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
  446. users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
  447. Enum.each(users, &User.force_password_reset_async/1)
  448. ModerationLog.insert_log(%{
  449. actor: admin,
  450. subject: users,
  451. action: "force_password_reset"
  452. })
  453. json_response(conn, :no_content, "")
  454. end
  455. @doc "Disable mfa for user's account."
  456. def disable_mfa(conn, %{"nickname" => nickname}) do
  457. case User.get_by_nickname(nickname) do
  458. %User{} = user ->
  459. MFA.disable(user)
  460. json(conn, nickname)
  461. _ ->
  462. {:error, :not_found}
  463. end
  464. end
  465. @doc "Show a given user's credentials"
  466. def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
  467. with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
  468. conn
  469. |> put_view(AccountView)
  470. |> render("credentials.json", %{user: user, for: admin})
  471. else
  472. _ -> {:error, :not_found}
  473. end
  474. end
  475. @doc "Updates a given user"
  476. def update_user_credentials(
  477. %{assigns: %{user: admin}} = conn,
  478. %{"nickname" => nickname} = params
  479. ) do
  480. with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
  481. {:ok, _user} <-
  482. User.update_as_admin(user, params) do
  483. ModerationLog.insert_log(%{
  484. actor: admin,
  485. subject: [user],
  486. action: "updated_users"
  487. })
  488. if params["password"] do
  489. User.force_password_reset_async(user)
  490. end
  491. ModerationLog.insert_log(%{
  492. actor: admin,
  493. subject: [user],
  494. action: "force_password_reset"
  495. })
  496. json(conn, %{status: "success"})
  497. else
  498. {:error, changeset} ->
  499. errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
  500. {:errors, errors}
  501. _ ->
  502. {:error, :not_found}
  503. end
  504. end
  505. def list_log(conn, params) do
  506. {page, page_size} = page_params(params)
  507. log =
  508. ModerationLog.get_all(%{
  509. page: page,
  510. page_size: page_size,
  511. start_date: params["start_date"],
  512. end_date: params["end_date"],
  513. user_id: params["user_id"],
  514. search: params["search"]
  515. })
  516. conn
  517. |> put_view(ModerationLogView)
  518. |> render("index.json", %{log: log})
  519. end
  520. def restart(conn, _params) do
  521. with :ok <- configurable_from_database() do
  522. Restarter.Pleroma.restart(Config.get(:env), 50)
  523. json(conn, %{})
  524. end
  525. end
  526. def need_reboot(conn, _params) do
  527. json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
  528. end
  529. defp configurable_from_database do
  530. if Config.get(:configurable_from_database) do
  531. :ok
  532. else
  533. {:error, "To use this endpoint you need to enable configuration from database."}
  534. end
  535. end
  536. def reload_emoji(conn, _params) do
  537. Pleroma.Emoji.reload()
  538. json(conn, "ok")
  539. end
  540. def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
  541. users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
  542. User.toggle_confirmation(users)
  543. ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
  544. json(conn, "")
  545. end
  546. def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
  547. users =
  548. Enum.map(nicknames, fn nickname ->
  549. nickname
  550. |> User.get_cached_by_nickname()
  551. |> User.send_confirmation_email()
  552. end)
  553. ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
  554. json(conn, "")
  555. end
  556. def stats(conn, params) do
  557. counters = Stats.get_status_visibility_count(params["instance"])
  558. json(conn, %{"status_visibility" => counters})
  559. end
  560. defp page_params(params) do
  561. {get_page(params["page"]), get_page_size(params["page_size"])}
  562. end
  563. defp get_page(page_string) when is_nil(page_string), do: 1
  564. defp get_page(page_string) do
  565. case Integer.parse(page_string) do
  566. {page, _} -> page
  567. :error -> 1
  568. end
  569. end
  570. defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
  571. defp get_page_size(page_size_string) do
  572. case Integer.parse(page_size_string) do
  573. {page_size, _} -> page_size
  574. :error -> @users_page_size
  575. end
  576. end
  577. end