Merge branch 'develop' into feature/account-export
[akkoma] / lib / pleroma / moderation_log.ex
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
5 defmodule Pleroma.ModerationLog do
6 use Ecto.Schema
7
8 alias Pleroma.Activity
9 alias Pleroma.ModerationLog
10 alias Pleroma.Repo
11 alias Pleroma.User
12
13 import Ecto.Query
14
15 schema "moderation_log" do
16 field(:data, :map)
17
18 timestamps()
19 end
20
21 def get_all(params) do
22 base_query =
23 get_all_query()
24 |> maybe_filter_by_date(params)
25 |> maybe_filter_by_user(params)
26 |> maybe_filter_by_search(params)
27
28 query_with_pagination = base_query |> paginate_query(params)
29
30 %{
31 items: Repo.all(query_with_pagination),
32 count: Repo.aggregate(base_query, :count, :id)
33 }
34 end
35
36 defp maybe_filter_by_date(query, %{start_date: nil, end_date: nil}), do: query
37
38 defp maybe_filter_by_date(query, %{start_date: start_date, end_date: nil}) do
39 from(q in query,
40 where: q.inserted_at >= ^parse_datetime(start_date)
41 )
42 end
43
44 defp maybe_filter_by_date(query, %{start_date: nil, end_date: end_date}) do
45 from(q in query,
46 where: q.inserted_at <= ^parse_datetime(end_date)
47 )
48 end
49
50 defp maybe_filter_by_date(query, %{start_date: start_date, end_date: end_date}) do
51 from(q in query,
52 where: q.inserted_at >= ^parse_datetime(start_date),
53 where: q.inserted_at <= ^parse_datetime(end_date)
54 )
55 end
56
57 defp maybe_filter_by_user(query, %{user_id: nil}), do: query
58
59 defp maybe_filter_by_user(query, %{user_id: user_id}) do
60 from(q in query,
61 where: fragment("(?)->'actor'->>'id' = ?", q.data, ^user_id)
62 )
63 end
64
65 defp maybe_filter_by_search(query, %{search: search}) when is_nil(search) or search == "",
66 do: query
67
68 defp maybe_filter_by_search(query, %{search: search}) do
69 from(q in query,
70 where: fragment("(?)->>'message' ILIKE ?", q.data, ^"%#{search}%")
71 )
72 end
73
74 defp paginate_query(query, %{page: page, page_size: page_size}) do
75 from(q in query,
76 limit: ^page_size,
77 offset: ^((page - 1) * page_size)
78 )
79 end
80
81 defp get_all_query do
82 from(q in __MODULE__,
83 order_by: [desc: q.inserted_at]
84 )
85 end
86
87 defp parse_datetime(datetime) do
88 {:ok, parsed_datetime, _} = DateTime.from_iso8601(datetime)
89
90 parsed_datetime
91 end
92
93 @spec insert_log(%{actor: User, subject: [User], action: String.t(), permission: String.t()}) ::
94 {:ok, ModerationLog} | {:error, any}
95 def insert_log(%{
96 actor: %User{} = actor,
97 subject: subjects,
98 action: action,
99 permission: permission
100 }) do
101 %ModerationLog{
102 data: %{
103 "actor" => user_to_map(actor),
104 "subject" => user_to_map(subjects),
105 "action" => action,
106 "permission" => permission,
107 "message" => ""
108 }
109 }
110 |> insert_log_entry_with_message()
111 end
112
113 @spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
114 {:ok, ModerationLog} | {:error, any}
115 def insert_log(%{
116 actor: %User{} = actor,
117 action: "report_update",
118 subject: %Activity{data: %{"type" => "Flag"}} = subject
119 }) do
120 %ModerationLog{
121 data: %{
122 "actor" => user_to_map(actor),
123 "action" => "report_update",
124 "subject" => report_to_map(subject),
125 "message" => ""
126 }
127 }
128 |> insert_log_entry_with_message()
129 end
130
131 @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) ::
132 {:ok, ModerationLog} | {:error, any}
133 def insert_log(%{
134 actor: %User{} = actor,
135 action: "report_note",
136 subject: %Activity{} = subject,
137 text: text
138 }) do
139 %ModerationLog{
140 data: %{
141 "actor" => user_to_map(actor),
142 "action" => "report_note",
143 "subject" => report_to_map(subject),
144 "text" => text
145 }
146 }
147 |> insert_log_entry_with_message()
148 end
149
150 @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) ::
151 {:ok, ModerationLog} | {:error, any}
152 def insert_log(%{
153 actor: %User{} = actor,
154 action: "report_note_delete",
155 subject: %Activity{} = subject,
156 text: text
157 }) do
158 %ModerationLog{
159 data: %{
160 "actor" => user_to_map(actor),
161 "action" => "report_note_delete",
162 "subject" => report_to_map(subject),
163 "text" => text
164 }
165 }
166 |> insert_log_entry_with_message()
167 end
168
169 @spec insert_log(%{
170 actor: User,
171 subject: Activity,
172 action: String.t(),
173 sensitive: String.t(),
174 visibility: String.t()
175 }) :: {:ok, ModerationLog} | {:error, any}
176 def insert_log(%{
177 actor: %User{} = actor,
178 action: "status_update",
179 subject: %Activity{} = subject,
180 sensitive: sensitive,
181 visibility: visibility
182 }) do
183 %ModerationLog{
184 data: %{
185 "actor" => user_to_map(actor),
186 "action" => "status_update",
187 "subject" => status_to_map(subject),
188 "sensitive" => sensitive,
189 "visibility" => visibility,
190 "message" => ""
191 }
192 }
193 |> insert_log_entry_with_message()
194 end
195
196 @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
197 {:ok, ModerationLog} | {:error, any}
198 def insert_log(%{
199 actor: %User{} = actor,
200 action: "status_delete",
201 subject_id: subject_id
202 }) do
203 %ModerationLog{
204 data: %{
205 "actor" => user_to_map(actor),
206 "action" => "status_delete",
207 "subject_id" => subject_id,
208 "message" => ""
209 }
210 }
211 |> insert_log_entry_with_message()
212 end
213
214 @spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
215 {:ok, ModerationLog} | {:error, any}
216 def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do
217 %ModerationLog{
218 data: %{
219 "actor" => user_to_map(actor),
220 "action" => action,
221 "subject" => user_to_map(subject),
222 "message" => ""
223 }
224 }
225 |> insert_log_entry_with_message()
226 end
227
228 @spec insert_log(%{actor: User, subjects: [User], action: String.t()}) ::
229 {:ok, ModerationLog} | {:error, any}
230 def insert_log(%{actor: %User{} = actor, subjects: subjects, action: action}) do
231 subjects = Enum.map(subjects, &user_to_map/1)
232
233 %ModerationLog{
234 data: %{
235 "actor" => user_to_map(actor),
236 "action" => action,
237 "subjects" => subjects,
238 "message" => ""
239 }
240 }
241 |> insert_log_entry_with_message()
242 end
243
244 @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
245 {:ok, ModerationLog} | {:error, any}
246 def insert_log(%{
247 actor: %User{} = actor,
248 followed: %User{} = followed,
249 follower: %User{} = follower,
250 action: "follow"
251 }) do
252 %ModerationLog{
253 data: %{
254 "actor" => user_to_map(actor),
255 "action" => "follow",
256 "followed" => user_to_map(followed),
257 "follower" => user_to_map(follower),
258 "message" => ""
259 }
260 }
261 |> insert_log_entry_with_message()
262 end
263
264 @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
265 {:ok, ModerationLog} | {:error, any}
266 def insert_log(%{
267 actor: %User{} = actor,
268 followed: %User{} = followed,
269 follower: %User{} = follower,
270 action: "unfollow"
271 }) do
272 %ModerationLog{
273 data: %{
274 "actor" => user_to_map(actor),
275 "action" => "unfollow",
276 "followed" => user_to_map(followed),
277 "follower" => user_to_map(follower),
278 "message" => ""
279 }
280 }
281 |> insert_log_entry_with_message()
282 end
283
284 @spec insert_log(%{
285 actor: User,
286 action: String.t(),
287 nicknames: [String.t()],
288 tags: [String.t()]
289 }) :: {:ok, ModerationLog} | {:error, any}
290 def insert_log(%{
291 actor: %User{} = actor,
292 nicknames: nicknames,
293 tags: tags,
294 action: action
295 }) do
296 %ModerationLog{
297 data: %{
298 "actor" => user_to_map(actor),
299 "nicknames" => nicknames,
300 "tags" => tags,
301 "action" => action,
302 "message" => ""
303 }
304 }
305 |> insert_log_entry_with_message()
306 end
307
308 @spec insert_log(%{actor: User, action: String.t(), target: String.t()}) ::
309 {:ok, ModerationLog} | {:error, any}
310 def insert_log(%{
311 actor: %User{} = actor,
312 action: action,
313 target: target
314 })
315 when action in ["relay_follow", "relay_unfollow"] do
316 %ModerationLog{
317 data: %{
318 "actor" => user_to_map(actor),
319 "action" => action,
320 "target" => target,
321 "message" => ""
322 }
323 }
324 |> insert_log_entry_with_message()
325 end
326
327 @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
328 {:ok, ModerationLog} | {:error, any}
329 def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do
330 %ModerationLog{
331 data: %{
332 "actor" => %{"nickname" => actor.nickname},
333 "action" => "chat_message_delete",
334 "subject_id" => subject_id
335 }
336 }
337 |> insert_log_entry_with_message()
338 end
339
340 @spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any}
341 defp insert_log_entry_with_message(entry) do
342 entry.data["message"]
343 |> put_in(get_log_entry_message(entry))
344 |> Repo.insert()
345 end
346
347 defp user_to_map(users) when is_list(users) do
348 users |> Enum.map(&user_to_map/1)
349 end
350
351 defp user_to_map(%User{} = user) do
352 user
353 |> Map.from_struct()
354 |> Map.take([:id, :nickname])
355 |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
356 |> Map.put("type", "user")
357 end
358
359 defp report_to_map(%Activity{} = report) do
360 %{
361 "type" => "report",
362 "id" => report.id,
363 "state" => report.data["state"]
364 }
365 end
366
367 defp status_to_map(%Activity{} = status) do
368 %{
369 "type" => "status",
370 "id" => status.id
371 }
372 end
373
374 def get_log_entry_message(%ModerationLog{
375 data: %{
376 "actor" => %{"nickname" => actor_nickname},
377 "action" => action,
378 "followed" => %{"nickname" => followed_nickname},
379 "follower" => %{"nickname" => follower_nickname}
380 }
381 }) do
382 "@#{actor_nickname} made @#{follower_nickname} #{action} @#{followed_nickname}"
383 end
384
385 @spec get_log_entry_message(ModerationLog) :: String.t()
386 def get_log_entry_message(%ModerationLog{
387 data: %{
388 "actor" => %{"nickname" => actor_nickname},
389 "action" => "delete",
390 "subject" => subjects
391 }
392 }) do
393 "@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}"
394 end
395
396 @spec get_log_entry_message(ModerationLog) :: String.t()
397 def get_log_entry_message(%ModerationLog{
398 data: %{
399 "actor" => %{"nickname" => actor_nickname},
400 "action" => "create",
401 "subjects" => subjects
402 }
403 }) do
404 "@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}"
405 end
406
407 @spec get_log_entry_message(ModerationLog) :: String.t()
408 def get_log_entry_message(%ModerationLog{
409 data: %{
410 "actor" => %{"nickname" => actor_nickname},
411 "action" => "activate",
412 "subject" => users
413 }
414 }) do
415 "@#{actor_nickname} activated users: #{users_to_nicknames_string(users)}"
416 end
417
418 @spec get_log_entry_message(ModerationLog) :: String.t()
419 def get_log_entry_message(%ModerationLog{
420 data: %{
421 "actor" => %{"nickname" => actor_nickname},
422 "action" => "deactivate",
423 "subject" => users
424 }
425 }) do
426 "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
427 end
428
429 @spec get_log_entry_message(ModerationLog) :: String.t()
430 def get_log_entry_message(%ModerationLog{
431 data: %{
432 "actor" => %{"nickname" => actor_nickname},
433 "action" => "approve",
434 "subject" => users
435 }
436 }) do
437 "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
438 end
439
440 @spec get_log_entry_message(ModerationLog) :: String.t()
441 def get_log_entry_message(%ModerationLog{
442 data: %{
443 "actor" => %{"nickname" => actor_nickname},
444 "nicknames" => nicknames,
445 "tags" => tags,
446 "action" => "tag"
447 }
448 }) do
449 tags_string = tags |> Enum.join(", ")
450
451 "@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}"
452 end
453
454 @spec get_log_entry_message(ModerationLog) :: String.t()
455 def get_log_entry_message(%ModerationLog{
456 data: %{
457 "actor" => %{"nickname" => actor_nickname},
458 "nicknames" => nicknames,
459 "tags" => tags,
460 "action" => "untag"
461 }
462 }) do
463 tags_string = tags |> Enum.join(", ")
464
465 "@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}"
466 end
467
468 @spec get_log_entry_message(ModerationLog) :: String.t()
469 def get_log_entry_message(%ModerationLog{
470 data: %{
471 "actor" => %{"nickname" => actor_nickname},
472 "action" => "grant",
473 "subject" => users,
474 "permission" => permission
475 }
476 }) do
477 "@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}"
478 end
479
480 @spec get_log_entry_message(ModerationLog) :: String.t()
481 def get_log_entry_message(%ModerationLog{
482 data: %{
483 "actor" => %{"nickname" => actor_nickname},
484 "action" => "revoke",
485 "subject" => users,
486 "permission" => permission
487 }
488 }) do
489 "@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}"
490 end
491
492 @spec get_log_entry_message(ModerationLog) :: String.t()
493 def get_log_entry_message(%ModerationLog{
494 data: %{
495 "actor" => %{"nickname" => actor_nickname},
496 "action" => "relay_follow",
497 "target" => target
498 }
499 }) do
500 "@#{actor_nickname} followed relay: #{target}"
501 end
502
503 @spec get_log_entry_message(ModerationLog) :: String.t()
504 def get_log_entry_message(%ModerationLog{
505 data: %{
506 "actor" => %{"nickname" => actor_nickname},
507 "action" => "relay_unfollow",
508 "target" => target
509 }
510 }) do
511 "@#{actor_nickname} unfollowed relay: #{target}"
512 end
513
514 @spec get_log_entry_message(ModerationLog) :: String.t()
515 def get_log_entry_message(%ModerationLog{
516 data: %{
517 "actor" => %{"nickname" => actor_nickname},
518 "action" => "report_update",
519 "subject" => %{"id" => subject_id, "state" => state, "type" => "report"}
520 }
521 }) do
522 "@#{actor_nickname} updated report ##{subject_id} with '#{state}' state"
523 end
524
525 @spec get_log_entry_message(ModerationLog) :: String.t()
526 def get_log_entry_message(%ModerationLog{
527 data: %{
528 "actor" => %{"nickname" => actor_nickname},
529 "action" => "report_note",
530 "subject" => %{"id" => subject_id, "type" => "report"},
531 "text" => text
532 }
533 }) do
534 "@#{actor_nickname} added note '#{text}' to report ##{subject_id}"
535 end
536
537 @spec get_log_entry_message(ModerationLog) :: String.t()
538 def get_log_entry_message(%ModerationLog{
539 data: %{
540 "actor" => %{"nickname" => actor_nickname},
541 "action" => "report_note_delete",
542 "subject" => %{"id" => subject_id, "type" => "report"},
543 "text" => text
544 }
545 }) do
546 "@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}"
547 end
548
549 @spec get_log_entry_message(ModerationLog) :: String.t()
550 def get_log_entry_message(%ModerationLog{
551 data: %{
552 "actor" => %{"nickname" => actor_nickname},
553 "action" => "status_update",
554 "subject" => %{"id" => subject_id, "type" => "status"},
555 "sensitive" => nil,
556 "visibility" => visibility
557 }
558 }) do
559 "@#{actor_nickname} updated status ##{subject_id}, set visibility: '#{visibility}'"
560 end
561
562 @spec get_log_entry_message(ModerationLog) :: String.t()
563 def get_log_entry_message(%ModerationLog{
564 data: %{
565 "actor" => %{"nickname" => actor_nickname},
566 "action" => "status_update",
567 "subject" => %{"id" => subject_id, "type" => "status"},
568 "sensitive" => sensitive,
569 "visibility" => nil
570 }
571 }) do
572 "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}'"
573 end
574
575 @spec get_log_entry_message(ModerationLog) :: String.t()
576 def get_log_entry_message(%ModerationLog{
577 data: %{
578 "actor" => %{"nickname" => actor_nickname},
579 "action" => "status_update",
580 "subject" => %{"id" => subject_id, "type" => "status"},
581 "sensitive" => sensitive,
582 "visibility" => visibility
583 }
584 }) do
585 "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{
586 visibility
587 }'"
588 end
589
590 @spec get_log_entry_message(ModerationLog) :: String.t()
591 def get_log_entry_message(%ModerationLog{
592 data: %{
593 "actor" => %{"nickname" => actor_nickname},
594 "action" => "status_delete",
595 "subject_id" => subject_id
596 }
597 }) do
598 "@#{actor_nickname} deleted status ##{subject_id}"
599 end
600
601 @spec get_log_entry_message(ModerationLog) :: String.t()
602 def get_log_entry_message(%ModerationLog{
603 data: %{
604 "actor" => %{"nickname" => actor_nickname},
605 "action" => "force_password_reset",
606 "subject" => subjects
607 }
608 }) do
609 "@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
610 end
611
612 @spec get_log_entry_message(ModerationLog) :: String.t()
613 def get_log_entry_message(%ModerationLog{
614 data: %{
615 "actor" => %{"nickname" => actor_nickname},
616 "action" => "confirm_email",
617 "subject" => subjects
618 }
619 }) do
620 "@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
621 end
622
623 @spec get_log_entry_message(ModerationLog) :: String.t()
624 def get_log_entry_message(%ModerationLog{
625 data: %{
626 "actor" => %{"nickname" => actor_nickname},
627 "action" => "resend_confirmation_email",
628 "subject" => subjects
629 }
630 }) do
631 "@#{actor_nickname} re-sent confirmation email for users: #{
632 users_to_nicknames_string(subjects)
633 }"
634 end
635
636 @spec get_log_entry_message(ModerationLog) :: String.t()
637 def get_log_entry_message(%ModerationLog{
638 data: %{
639 "actor" => %{"nickname" => actor_nickname},
640 "action" => "updated_users",
641 "subject" => subjects
642 }
643 }) do
644 "@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
645 end
646
647 @spec get_log_entry_message(ModerationLog) :: String.t()
648 def get_log_entry_message(%ModerationLog{
649 data: %{
650 "actor" => %{"nickname" => actor_nickname},
651 "action" => "chat_message_delete",
652 "subject_id" => subject_id
653 }
654 }) do
655 "@#{actor_nickname} deleted chat message ##{subject_id}"
656 end
657
658 def get_log_entry_message(%ModerationLog{
659 data: %{
660 "actor" => %{"nickname" => actor_nickname},
661 "action" => "create_backup",
662 "subject" => %{"nickname" => user_nickname}
663 }
664 }) do
665 "@#{actor_nickname} requested account backup for @#{user_nickname}"
666 end
667
668 defp nicknames_to_string(nicknames) do
669 nicknames
670 |> Enum.map(&"@#{&1}")
671 |> Enum.join(", ")
672 end
673
674 defp users_to_nicknames_string(users) do
675 users
676 |> Enum.map(&"@#{&1["nickname"]}")
677 |> Enum.join(", ")
678 end
679 end