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.

585 lines
16KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.ModerationLog do
  5. use Ecto.Schema
  6. alias Pleroma.Activity
  7. alias Pleroma.ModerationLog
  8. alias Pleroma.Repo
  9. alias Pleroma.User
  10. import Ecto.Query
  11. @type t :: %__MODULE__{}
  12. @type log_subject :: Activity.t() | User.t() | list(User.t())
  13. @type log_params :: %{
  14. required(:actor) => User.t(),
  15. required(:action) => String.t(),
  16. optional(:subject) => log_subject(),
  17. optional(:subject_actor) => User.t(),
  18. optional(:subject_id) => String.t(),
  19. optional(:subjects) => list(User.t()),
  20. optional(:permission) => String.t(),
  21. optional(:text) => String.t(),
  22. optional(:sensitive) => String.t(),
  23. optional(:visibility) => String.t(),
  24. optional(:followed) => User.t(),
  25. optional(:follower) => User.t(),
  26. optional(:nicknames) => list(String.t()),
  27. optional(:tags) => list(String.t()),
  28. optional(:target) => String.t()
  29. }
  30. schema "moderation_log" do
  31. field(:data, :map)
  32. timestamps()
  33. end
  34. def get_all(params) do
  35. base_query =
  36. get_all_query()
  37. |> maybe_filter_by_date(params)
  38. |> maybe_filter_by_user(params)
  39. |> maybe_filter_by_search(params)
  40. query_with_pagination = base_query |> paginate_query(params)
  41. %{
  42. items: Repo.all(query_with_pagination),
  43. count: Repo.aggregate(base_query, :count, :id)
  44. }
  45. end
  46. defp maybe_filter_by_date(query, %{start_date: nil, end_date: nil}), do: query
  47. defp maybe_filter_by_date(query, %{start_date: start_date, end_date: nil}) do
  48. from(q in query,
  49. where: q.inserted_at >= ^parse_datetime(start_date)
  50. )
  51. end
  52. defp maybe_filter_by_date(query, %{start_date: nil, end_date: end_date}) do
  53. from(q in query,
  54. where: q.inserted_at <= ^parse_datetime(end_date)
  55. )
  56. end
  57. defp maybe_filter_by_date(query, %{start_date: start_date, end_date: end_date}) do
  58. from(q in query,
  59. where: q.inserted_at >= ^parse_datetime(start_date),
  60. where: q.inserted_at <= ^parse_datetime(end_date)
  61. )
  62. end
  63. defp maybe_filter_by_user(query, %{user_id: nil}), do: query
  64. defp maybe_filter_by_user(query, %{user_id: user_id}) do
  65. from(q in query,
  66. where: fragment("(?)->'actor'->>'id' = ?", q.data, ^user_id)
  67. )
  68. end
  69. defp maybe_filter_by_search(query, %{search: search}) when is_nil(search) or search == "",
  70. do: query
  71. defp maybe_filter_by_search(query, %{search: search}) do
  72. from(q in query,
  73. where: fragment("(?)->>'message' ILIKE ?", q.data, ^"%#{search}%")
  74. )
  75. end
  76. defp paginate_query(query, %{page: page, page_size: page_size}) do
  77. from(q in query,
  78. limit: ^page_size,
  79. offset: ^((page - 1) * page_size)
  80. )
  81. end
  82. defp get_all_query do
  83. from(q in __MODULE__,
  84. order_by: [desc: q.inserted_at]
  85. )
  86. end
  87. defp parse_datetime(datetime) do
  88. {:ok, parsed_datetime, _} = DateTime.from_iso8601(datetime)
  89. parsed_datetime
  90. end
  91. defp prepare_log_data(%{actor: actor, action: action} = attrs) do
  92. %{
  93. "actor" => user_to_map(actor),
  94. "action" => action,
  95. "message" => ""
  96. }
  97. |> Pleroma.Maps.put_if_present("subject_actor", user_to_map(attrs[:subject_actor]))
  98. end
  99. defp prepare_log_data(attrs), do: attrs
  100. @spec insert_log(log_params()) :: {:ok, ModerationLog} | {:error, any}
  101. def insert_log(%{actor: %User{}, subject: subjects, permission: permission} = attrs) do
  102. data =
  103. attrs
  104. |> prepare_log_data
  105. |> Map.merge(%{"subject" => user_to_map(subjects), "permission" => permission})
  106. insert_log_entry_with_message(%ModerationLog{data: data})
  107. end
  108. def insert_log(%{actor: %User{}, action: action, subject: %Activity{} = subject} = attrs)
  109. when action in ["report_note_delete", "report_update", "report_note"] do
  110. data =
  111. attrs
  112. |> prepare_log_data
  113. |> Pleroma.Maps.put_if_present("text", attrs[:text])
  114. |> Map.merge(%{"subject" => report_to_map(subject)})
  115. insert_log_entry_with_message(%ModerationLog{data: data})
  116. end
  117. def insert_log(
  118. %{
  119. actor: %User{},
  120. action: action,
  121. subject: %Activity{} = subject,
  122. sensitive: sensitive,
  123. visibility: visibility
  124. } = attrs
  125. )
  126. when action == "status_update" do
  127. data =
  128. attrs
  129. |> prepare_log_data
  130. |> Map.merge(%{
  131. "subject" => status_to_map(subject),
  132. "sensitive" => sensitive,
  133. "visibility" => visibility
  134. })
  135. insert_log_entry_with_message(%ModerationLog{data: data})
  136. end
  137. def insert_log(%{actor: %User{}, action: action, subject_id: subject_id} = attrs)
  138. when action == "status_delete" do
  139. data =
  140. attrs
  141. |> prepare_log_data
  142. |> Map.merge(%{"subject_id" => subject_id})
  143. insert_log_entry_with_message(%ModerationLog{data: data})
  144. end
  145. def insert_log(%{actor: %User{}, subject: subject, action: _action} = attrs) do
  146. data =
  147. attrs
  148. |> prepare_log_data
  149. |> Map.merge(%{"subject" => user_to_map(subject)})
  150. insert_log_entry_with_message(%ModerationLog{data: data})
  151. end
  152. def insert_log(%{actor: %User{}, subjects: subjects, action: _action} = attrs) do
  153. data =
  154. attrs
  155. |> prepare_log_data
  156. |> Map.merge(%{"subjects" => user_to_map(subjects)})
  157. insert_log_entry_with_message(%ModerationLog{data: data})
  158. end
  159. def insert_log(
  160. %{
  161. actor: %User{},
  162. followed: %User{} = followed,
  163. follower: %User{} = follower,
  164. action: action
  165. } = attrs
  166. )
  167. when action in ["unfollow", "follow"] do
  168. data =
  169. attrs
  170. |> prepare_log_data
  171. |> Map.merge(%{"followed" => user_to_map(followed), "follower" => user_to_map(follower)})
  172. insert_log_entry_with_message(%ModerationLog{data: data})
  173. end
  174. def insert_log(%{
  175. actor: %User{} = actor,
  176. nicknames: nicknames,
  177. tags: tags,
  178. action: action
  179. }) do
  180. %ModerationLog{
  181. data: %{
  182. "actor" => user_to_map(actor),
  183. "nicknames" => nicknames,
  184. "tags" => tags,
  185. "action" => action,
  186. "message" => ""
  187. }
  188. }
  189. |> insert_log_entry_with_message()
  190. end
  191. def insert_log(%{actor: %User{}, action: action, target: target} = attrs)
  192. when action in ["relay_follow", "relay_unfollow"] do
  193. data =
  194. attrs
  195. |> prepare_log_data
  196. |> Map.merge(%{"target" => target})
  197. insert_log_entry_with_message(%ModerationLog{data: data})
  198. end
  199. def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do
  200. %ModerationLog{
  201. data: %{
  202. "actor" => %{"nickname" => actor.nickname},
  203. "action" => "chat_message_delete",
  204. "subject_id" => subject_id
  205. }
  206. }
  207. |> insert_log_entry_with_message()
  208. end
  209. @spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any}
  210. defp insert_log_entry_with_message(entry) do
  211. entry.data["message"]
  212. |> put_in(get_log_entry_message(entry))
  213. |> Repo.insert()
  214. end
  215. defp user_to_map(users) when is_list(users) do
  216. Enum.map(users, &user_to_map/1)
  217. end
  218. defp user_to_map(%User{} = user) do
  219. user
  220. |> Map.take([:id, :nickname])
  221. |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
  222. |> Map.put("type", "user")
  223. end
  224. defp user_to_map(_), do: nil
  225. defp report_to_map(%Activity{} = report) do
  226. %{"type" => "report", "id" => report.id, "state" => report.data["state"]}
  227. end
  228. defp status_to_map(%Activity{} = status) do
  229. %{"type" => "status", "id" => status.id}
  230. end
  231. @spec get_log_entry_message(ModerationLog.t()) :: String.t()
  232. def get_log_entry_message(%ModerationLog{
  233. data: %{
  234. "actor" => %{"nickname" => actor_nickname},
  235. "action" => action,
  236. "followed" => %{"nickname" => followed_nickname},
  237. "follower" => %{"nickname" => follower_nickname}
  238. }
  239. }) do
  240. "@#{actor_nickname} made @#{follower_nickname} #{action} @#{followed_nickname}"
  241. end
  242. def get_log_entry_message(%ModerationLog{
  243. data: %{
  244. "actor" => %{"nickname" => actor_nickname},
  245. "action" => "delete",
  246. "subject" => subjects
  247. }
  248. }) do
  249. "@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}"
  250. end
  251. def get_log_entry_message(%ModerationLog{
  252. data: %{
  253. "actor" => %{"nickname" => actor_nickname},
  254. "action" => "create",
  255. "subjects" => subjects
  256. }
  257. }) do
  258. "@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}"
  259. end
  260. def get_log_entry_message(%ModerationLog{
  261. data: %{
  262. "actor" => %{"nickname" => actor_nickname},
  263. "action" => "activate",
  264. "subject" => users
  265. }
  266. }) do
  267. "@#{actor_nickname} activated users: #{users_to_nicknames_string(users)}"
  268. end
  269. def get_log_entry_message(%ModerationLog{
  270. data: %{
  271. "actor" => %{"nickname" => actor_nickname},
  272. "action" => "deactivate",
  273. "subject" => users
  274. }
  275. }) do
  276. "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
  277. end
  278. def get_log_entry_message(%ModerationLog{
  279. data: %{
  280. "actor" => %{"nickname" => actor_nickname},
  281. "action" => "approve",
  282. "subject" => users
  283. }
  284. }) do
  285. "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
  286. end
  287. def get_log_entry_message(%ModerationLog{
  288. data: %{
  289. "actor" => %{"nickname" => actor_nickname},
  290. "nicknames" => nicknames,
  291. "tags" => tags,
  292. "action" => "tag"
  293. }
  294. }) do
  295. tags_string = tags |> Enum.join(", ")
  296. "@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}"
  297. end
  298. def get_log_entry_message(%ModerationLog{
  299. data: %{
  300. "actor" => %{"nickname" => actor_nickname},
  301. "nicknames" => nicknames,
  302. "tags" => tags,
  303. "action" => "untag"
  304. }
  305. }) do
  306. tags_string = tags |> Enum.join(", ")
  307. "@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}"
  308. end
  309. def get_log_entry_message(%ModerationLog{
  310. data: %{
  311. "actor" => %{"nickname" => actor_nickname},
  312. "action" => "grant",
  313. "subject" => users,
  314. "permission" => permission
  315. }
  316. }) do
  317. "@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}"
  318. end
  319. def get_log_entry_message(%ModerationLog{
  320. data: %{
  321. "actor" => %{"nickname" => actor_nickname},
  322. "action" => "revoke",
  323. "subject" => users,
  324. "permission" => permission
  325. }
  326. }) do
  327. "@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}"
  328. end
  329. def get_log_entry_message(%ModerationLog{
  330. data: %{
  331. "actor" => %{"nickname" => actor_nickname},
  332. "action" => "relay_follow",
  333. "target" => target
  334. }
  335. }) do
  336. "@#{actor_nickname} followed relay: #{target}"
  337. end
  338. def get_log_entry_message(%ModerationLog{
  339. data: %{
  340. "actor" => %{"nickname" => actor_nickname},
  341. "action" => "relay_unfollow",
  342. "target" => target
  343. }
  344. }) do
  345. "@#{actor_nickname} unfollowed relay: #{target}"
  346. end
  347. def get_log_entry_message(
  348. %ModerationLog{
  349. data: %{
  350. "actor" => %{"nickname" => actor_nickname},
  351. "action" => "report_update",
  352. "subject" => %{"id" => subject_id, "state" => state, "type" => "report"}
  353. }
  354. } = log
  355. ) do
  356. "@#{actor_nickname} updated report ##{subject_id}" <>
  357. subject_actor_nickname(log, " (on user ", ")") <>
  358. " with '#{state}' state"
  359. end
  360. def get_log_entry_message(
  361. %ModerationLog{
  362. data: %{
  363. "actor" => %{"nickname" => actor_nickname},
  364. "action" => "report_note",
  365. "subject" => %{"id" => subject_id, "type" => "report"},
  366. "text" => text
  367. }
  368. } = log
  369. ) do
  370. "@#{actor_nickname} added note '#{text}' to report ##{subject_id}" <>
  371. subject_actor_nickname(log, " on user ")
  372. end
  373. def get_log_entry_message(
  374. %ModerationLog{
  375. data: %{
  376. "actor" => %{"nickname" => actor_nickname},
  377. "action" => "report_note_delete",
  378. "subject" => %{"id" => subject_id, "type" => "report"},
  379. "text" => text
  380. }
  381. } = log
  382. ) do
  383. "@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}" <>
  384. subject_actor_nickname(log, " on user ")
  385. end
  386. def get_log_entry_message(%ModerationLog{
  387. data: %{
  388. "actor" => %{"nickname" => actor_nickname},
  389. "action" => "status_update",
  390. "subject" => %{"id" => subject_id, "type" => "status"},
  391. "sensitive" => nil,
  392. "visibility" => visibility
  393. }
  394. }) do
  395. "@#{actor_nickname} updated status ##{subject_id}, set visibility: '#{visibility}'"
  396. end
  397. def get_log_entry_message(%ModerationLog{
  398. data: %{
  399. "actor" => %{"nickname" => actor_nickname},
  400. "action" => "status_update",
  401. "subject" => %{"id" => subject_id, "type" => "status"},
  402. "sensitive" => sensitive,
  403. "visibility" => nil
  404. }
  405. }) do
  406. "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}'"
  407. end
  408. def get_log_entry_message(%ModerationLog{
  409. data: %{
  410. "actor" => %{"nickname" => actor_nickname},
  411. "action" => "status_update",
  412. "subject" => %{"id" => subject_id, "type" => "status"},
  413. "sensitive" => sensitive,
  414. "visibility" => visibility
  415. }
  416. }) do
  417. "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{
  418. visibility
  419. }'"
  420. end
  421. def get_log_entry_message(%ModerationLog{
  422. data: %{
  423. "actor" => %{"nickname" => actor_nickname},
  424. "action" => "status_delete",
  425. "subject_id" => subject_id
  426. }
  427. }) do
  428. "@#{actor_nickname} deleted status ##{subject_id}"
  429. end
  430. def get_log_entry_message(%ModerationLog{
  431. data: %{
  432. "actor" => %{"nickname" => actor_nickname},
  433. "action" => "force_password_reset",
  434. "subject" => subjects
  435. }
  436. }) do
  437. "@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
  438. end
  439. def get_log_entry_message(%ModerationLog{
  440. data: %{
  441. "actor" => %{"nickname" => actor_nickname},
  442. "action" => "confirm_email",
  443. "subject" => subjects
  444. }
  445. }) do
  446. "@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
  447. end
  448. def get_log_entry_message(%ModerationLog{
  449. data: %{
  450. "actor" => %{"nickname" => actor_nickname},
  451. "action" => "resend_confirmation_email",
  452. "subject" => subjects
  453. }
  454. }) do
  455. "@#{actor_nickname} re-sent confirmation email for users: #{
  456. users_to_nicknames_string(subjects)
  457. }"
  458. end
  459. def get_log_entry_message(%ModerationLog{
  460. data: %{
  461. "actor" => %{"nickname" => actor_nickname},
  462. "action" => "updated_users",
  463. "subject" => subjects
  464. }
  465. }) do
  466. "@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
  467. end
  468. def get_log_entry_message(%ModerationLog{
  469. data: %{
  470. "actor" => %{"nickname" => actor_nickname},
  471. "action" => "chat_message_delete",
  472. "subject_id" => subject_id
  473. }
  474. }) do
  475. "@#{actor_nickname} deleted chat message ##{subject_id}"
  476. end
  477. def get_log_entry_message(%ModerationLog{
  478. data: %{
  479. "actor" => %{"nickname" => actor_nickname},
  480. "action" => "create_backup",
  481. "subject" => %{"nickname" => user_nickname}
  482. }
  483. }) do
  484. "@#{actor_nickname} requested account backup for @#{user_nickname}"
  485. end
  486. defp nicknames_to_string(nicknames) do
  487. nicknames
  488. |> Enum.map(&"@#{&1}")
  489. |> Enum.join(", ")
  490. end
  491. defp users_to_nicknames_string(users) do
  492. users
  493. |> Enum.map(&"@#{&1["nickname"]}")
  494. |> Enum.join(", ")
  495. end
  496. defp subject_actor_nickname(%ModerationLog{data: data}, prefix_msg, postfix_msg \\ "") do
  497. case data do
  498. %{"subject_actor" => %{"nickname" => subject_actor}} ->
  499. [prefix_msg, "@#{subject_actor}", postfix_msg]
  500. |> Enum.reject(&(&1 == ""))
  501. |> Enum.join()
  502. _ ->
  503. ""
  504. end
  505. end
  506. end