logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

moderation_log.ex (17429B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2022 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.t()} | {: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.t()) ::
  210. {:ok, ModerationLog.t()} | {:error, any}
  211. defp insert_log_entry_with_message(entry) do
  212. entry.data["message"]
  213. |> put_in(get_log_entry_message(entry))
  214. |> Repo.insert()
  215. end
  216. defp user_to_map(users) when is_list(users) do
  217. Enum.map(users, &user_to_map/1)
  218. end
  219. defp user_to_map(%User{} = user) do
  220. user
  221. |> Map.take([:id, :nickname])
  222. |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
  223. |> Map.put("type", "user")
  224. end
  225. defp user_to_map(_), do: nil
  226. defp report_to_map(%Activity{} = report) do
  227. %{"type" => "report", "id" => report.id, "state" => report.data["state"]}
  228. end
  229. defp status_to_map(%Activity{} = status) do
  230. %{"type" => "status", "id" => status.id}
  231. end
  232. @spec get_log_entry_message(ModerationLog.t()) :: String.t()
  233. def get_log_entry_message(%ModerationLog{
  234. data: %{
  235. "actor" => %{"nickname" => actor_nickname},
  236. "action" => action,
  237. "followed" => %{"nickname" => followed_nickname},
  238. "follower" => %{"nickname" => follower_nickname}
  239. }
  240. }) do
  241. "@#{actor_nickname} made @#{follower_nickname} #{action} @#{followed_nickname}"
  242. end
  243. def get_log_entry_message(%ModerationLog{
  244. data: %{
  245. "actor" => %{"nickname" => actor_nickname},
  246. "action" => "delete",
  247. "subject" => subjects
  248. }
  249. }) do
  250. "@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}"
  251. end
  252. def get_log_entry_message(%ModerationLog{
  253. data: %{
  254. "actor" => %{"nickname" => actor_nickname},
  255. "action" => "create",
  256. "subjects" => subjects
  257. }
  258. }) do
  259. "@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}"
  260. end
  261. def get_log_entry_message(%ModerationLog{
  262. data: %{
  263. "actor" => %{"nickname" => actor_nickname},
  264. "action" => "activate",
  265. "subject" => users
  266. }
  267. }) do
  268. "@#{actor_nickname} activated users: #{users_to_nicknames_string(users)}"
  269. end
  270. def get_log_entry_message(%ModerationLog{
  271. data: %{
  272. "actor" => %{"nickname" => actor_nickname},
  273. "action" => "deactivate",
  274. "subject" => users
  275. }
  276. }) do
  277. "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
  278. end
  279. def get_log_entry_message(%ModerationLog{
  280. data: %{
  281. "actor" => %{"nickname" => actor_nickname},
  282. "action" => "approve",
  283. "subject" => users
  284. }
  285. }) do
  286. "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
  287. end
  288. def get_log_entry_message(%ModerationLog{
  289. data: %{
  290. "actor" => %{"nickname" => actor_nickname},
  291. "action" => "add_suggestion",
  292. "subject" => users
  293. }
  294. }) do
  295. "@#{actor_nickname} added suggested users: #{users_to_nicknames_string(users)}"
  296. end
  297. def get_log_entry_message(%ModerationLog{
  298. data: %{
  299. "actor" => %{"nickname" => actor_nickname},
  300. "action" => "remove_suggestion",
  301. "subject" => users
  302. }
  303. }) do
  304. "@#{actor_nickname} removed suggested users: #{users_to_nicknames_string(users)}"
  305. end
  306. def get_log_entry_message(%ModerationLog{
  307. data: %{
  308. "actor" => %{"nickname" => actor_nickname},
  309. "nicknames" => nicknames,
  310. "tags" => tags,
  311. "action" => "tag"
  312. }
  313. }) do
  314. tags_string = tags |> Enum.join(", ")
  315. "@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}"
  316. end
  317. def get_log_entry_message(%ModerationLog{
  318. data: %{
  319. "actor" => %{"nickname" => actor_nickname},
  320. "nicknames" => nicknames,
  321. "tags" => tags,
  322. "action" => "untag"
  323. }
  324. }) do
  325. tags_string = tags |> Enum.join(", ")
  326. "@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}"
  327. end
  328. def get_log_entry_message(%ModerationLog{
  329. data: %{
  330. "actor" => %{"nickname" => actor_nickname},
  331. "action" => "grant",
  332. "subject" => users,
  333. "permission" => permission
  334. }
  335. }) do
  336. "@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}"
  337. end
  338. def get_log_entry_message(%ModerationLog{
  339. data: %{
  340. "actor" => %{"nickname" => actor_nickname},
  341. "action" => "revoke",
  342. "subject" => users,
  343. "permission" => permission
  344. }
  345. }) do
  346. "@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}"
  347. end
  348. def get_log_entry_message(%ModerationLog{
  349. data: %{
  350. "actor" => %{"nickname" => actor_nickname},
  351. "action" => "relay_follow",
  352. "target" => target
  353. }
  354. }) do
  355. "@#{actor_nickname} followed relay: #{target}"
  356. end
  357. def get_log_entry_message(%ModerationLog{
  358. data: %{
  359. "actor" => %{"nickname" => actor_nickname},
  360. "action" => "relay_unfollow",
  361. "target" => target
  362. }
  363. }) do
  364. "@#{actor_nickname} unfollowed relay: #{target}"
  365. end
  366. def get_log_entry_message(
  367. %ModerationLog{
  368. data: %{
  369. "actor" => %{"nickname" => actor_nickname},
  370. "action" => "report_update",
  371. "subject" => %{"id" => subject_id, "state" => state, "type" => "report"}
  372. }
  373. } = log
  374. ) do
  375. "@#{actor_nickname} updated report ##{subject_id}" <>
  376. subject_actor_nickname(log, " (on user ", ")") <>
  377. " with '#{state}' state"
  378. end
  379. def get_log_entry_message(
  380. %ModerationLog{
  381. data: %{
  382. "actor" => %{"nickname" => actor_nickname},
  383. "action" => "report_note",
  384. "subject" => %{"id" => subject_id, "type" => "report"},
  385. "text" => text
  386. }
  387. } = log
  388. ) do
  389. "@#{actor_nickname} added note '#{text}' to report ##{subject_id}" <>
  390. subject_actor_nickname(log, " on user ")
  391. end
  392. def get_log_entry_message(
  393. %ModerationLog{
  394. data: %{
  395. "actor" => %{"nickname" => actor_nickname},
  396. "action" => "report_note_delete",
  397. "subject" => %{"id" => subject_id, "type" => "report"},
  398. "text" => text
  399. }
  400. } = log
  401. ) do
  402. "@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}" <>
  403. subject_actor_nickname(log, " on user ")
  404. end
  405. def get_log_entry_message(%ModerationLog{
  406. data: %{
  407. "actor" => %{"nickname" => actor_nickname},
  408. "action" => "status_update",
  409. "subject" => %{"id" => subject_id, "type" => "status"},
  410. "sensitive" => nil,
  411. "visibility" => visibility
  412. }
  413. }) do
  414. "@#{actor_nickname} updated status ##{subject_id}, set visibility: '#{visibility}'"
  415. end
  416. def get_log_entry_message(%ModerationLog{
  417. data: %{
  418. "actor" => %{"nickname" => actor_nickname},
  419. "action" => "status_update",
  420. "subject" => %{"id" => subject_id, "type" => "status"},
  421. "sensitive" => sensitive,
  422. "visibility" => nil
  423. }
  424. }) do
  425. "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}'"
  426. end
  427. def get_log_entry_message(%ModerationLog{
  428. data: %{
  429. "actor" => %{"nickname" => actor_nickname},
  430. "action" => "status_update",
  431. "subject" => %{"id" => subject_id, "type" => "status"},
  432. "sensitive" => sensitive,
  433. "visibility" => visibility
  434. }
  435. }) do
  436. "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{visibility}'"
  437. end
  438. def get_log_entry_message(%ModerationLog{
  439. data: %{
  440. "actor" => %{"nickname" => actor_nickname},
  441. "action" => "status_delete",
  442. "subject_id" => subject_id
  443. }
  444. }) do
  445. "@#{actor_nickname} deleted status ##{subject_id}"
  446. end
  447. def get_log_entry_message(%ModerationLog{
  448. data: %{
  449. "actor" => %{"nickname" => actor_nickname},
  450. "action" => "force_password_reset",
  451. "subject" => subjects
  452. }
  453. }) do
  454. "@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
  455. end
  456. def get_log_entry_message(%ModerationLog{
  457. data: %{
  458. "actor" => %{"nickname" => actor_nickname},
  459. "action" => "confirm_email",
  460. "subject" => subjects
  461. }
  462. }) do
  463. "@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
  464. end
  465. def get_log_entry_message(%ModerationLog{
  466. data: %{
  467. "actor" => %{"nickname" => actor_nickname},
  468. "action" => "resend_confirmation_email",
  469. "subject" => subjects
  470. }
  471. }) do
  472. "@#{actor_nickname} re-sent confirmation email for users: #{users_to_nicknames_string(subjects)}"
  473. end
  474. def get_log_entry_message(%ModerationLog{
  475. data: %{
  476. "actor" => %{"nickname" => actor_nickname},
  477. "action" => "updated_users",
  478. "subject" => subjects
  479. }
  480. }) do
  481. "@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
  482. end
  483. def get_log_entry_message(%ModerationLog{
  484. data: %{
  485. "actor" => %{"nickname" => actor_nickname},
  486. "action" => "chat_message_delete",
  487. "subject_id" => subject_id
  488. }
  489. }) do
  490. "@#{actor_nickname} deleted chat message ##{subject_id}"
  491. end
  492. def get_log_entry_message(%ModerationLog{
  493. data: %{
  494. "actor" => %{"nickname" => actor_nickname},
  495. "action" => "create_backup",
  496. "subject" => %{"nickname" => user_nickname}
  497. }
  498. }) do
  499. "@#{actor_nickname} requested account backup for @#{user_nickname}"
  500. end
  501. defp nicknames_to_string(nicknames) do
  502. nicknames
  503. |> Enum.map(&"@#{&1}")
  504. |> Enum.join(", ")
  505. end
  506. defp users_to_nicknames_string(users) do
  507. users
  508. |> Enum.map(&"@#{&1["nickname"]}")
  509. |> Enum.join(", ")
  510. end
  511. defp subject_actor_nickname(%ModerationLog{data: data}, prefix_msg, postfix_msg \\ "") do
  512. case data do
  513. %{"subject_actor" => %{"nickname" => subject_actor}} ->
  514. [prefix_msg, "@#{subject_actor}", postfix_msg]
  515. |> Enum.reject(&(&1 == ""))
  516. |> Enum.join()
  517. _ ->
  518. ""
  519. end
  520. end
  521. end