0a701127f839629083a1edcaacdd7bd0cde3e17f
[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 %{
117 actor: %User{} = actor,
118 action: "report_update",
119 subject: %Activity{data: %{"type" => "Flag"}} = subject
120 } = attrs
121 ) do
122 %ModerationLog{
123 data: %{
124 "actor" => user_to_map(actor),
125 "action" => "report_update",
126 "subject" => report_to_map(subject),
127 "subject_actor" => user_to_map(attrs[:subject_actor]),
128 "message" => ""
129 }
130 }
131 |> insert_log_entry_with_message()
132 end
133
134 @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) ::
135 {:ok, ModerationLog} | {:error, any}
136 def insert_log(
137 %{
138 actor: %User{} = actor,
139 action: "report_note",
140 subject: %Activity{} = subject,
141 text: text
142 } = attrs
143 ) do
144 %ModerationLog{
145 data: %{
146 "actor" => user_to_map(actor),
147 "action" => "report_note",
148 "subject" => report_to_map(subject),
149 "subject_actor" => user_to_map(attrs[:subject_actor]),
150 "text" => text
151 }
152 }
153 |> insert_log_entry_with_message()
154 end
155
156 @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) ::
157 {:ok, ModerationLog} | {:error, any}
158 def insert_log(
159 %{
160 actor: %User{} = actor,
161 action: "report_note_delete",
162 subject: %Activity{} = subject,
163 text: text
164 } = attrs
165 ) do
166 %ModerationLog{
167 data: %{
168 "actor" => user_to_map(actor),
169 "action" => "report_note_delete",
170 "subject" => report_to_map(subject),
171 "subject_actor" => user_to_map(attrs[:subject_actor]),
172 "text" => text
173 }
174 }
175 |> insert_log_entry_with_message()
176 end
177
178 @spec insert_log(%{
179 actor: User,
180 subject: Activity,
181 action: String.t(),
182 sensitive: String.t(),
183 visibility: String.t()
184 }) :: {:ok, ModerationLog} | {:error, any}
185 def insert_log(%{
186 actor: %User{} = actor,
187 action: "status_update",
188 subject: %Activity{} = subject,
189 sensitive: sensitive,
190 visibility: visibility
191 }) do
192 %ModerationLog{
193 data: %{
194 "actor" => user_to_map(actor),
195 "action" => "status_update",
196 "subject" => status_to_map(subject),
197 "sensitive" => sensitive,
198 "visibility" => visibility,
199 "message" => ""
200 }
201 }
202 |> insert_log_entry_with_message()
203 end
204
205 @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
206 {:ok, ModerationLog} | {:error, any}
207 def insert_log(%{
208 actor: %User{} = actor,
209 action: "status_delete",
210 subject_id: subject_id
211 }) do
212 %ModerationLog{
213 data: %{
214 "actor" => user_to_map(actor),
215 "action" => "status_delete",
216 "subject_id" => subject_id,
217 "message" => ""
218 }
219 }
220 |> insert_log_entry_with_message()
221 end
222
223 @spec insert_log(%{actor: User, subject: User, action: String.t()}) ::
224 {:ok, ModerationLog} | {:error, any}
225 def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do
226 %ModerationLog{
227 data: %{
228 "actor" => user_to_map(actor),
229 "action" => action,
230 "subject" => user_to_map(subject),
231 "message" => ""
232 }
233 }
234 |> insert_log_entry_with_message()
235 end
236
237 @spec insert_log(%{actor: User, subjects: [User], action: String.t()}) ::
238 {:ok, ModerationLog} | {:error, any}
239 def insert_log(%{actor: %User{} = actor, subjects: subjects, action: action}) do
240 subjects = Enum.map(subjects, &user_to_map/1)
241
242 %ModerationLog{
243 data: %{
244 "actor" => user_to_map(actor),
245 "action" => action,
246 "subjects" => subjects,
247 "message" => ""
248 }
249 }
250 |> insert_log_entry_with_message()
251 end
252
253 @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
254 {:ok, ModerationLog} | {:error, any}
255 def insert_log(%{
256 actor: %User{} = actor,
257 followed: %User{} = followed,
258 follower: %User{} = follower,
259 action: "follow"
260 }) do
261 %ModerationLog{
262 data: %{
263 "actor" => user_to_map(actor),
264 "action" => "follow",
265 "followed" => user_to_map(followed),
266 "follower" => user_to_map(follower),
267 "message" => ""
268 }
269 }
270 |> insert_log_entry_with_message()
271 end
272
273 @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) ::
274 {:ok, ModerationLog} | {:error, any}
275 def insert_log(%{
276 actor: %User{} = actor,
277 followed: %User{} = followed,
278 follower: %User{} = follower,
279 action: "unfollow"
280 }) do
281 %ModerationLog{
282 data: %{
283 "actor" => user_to_map(actor),
284 "action" => "unfollow",
285 "followed" => user_to_map(followed),
286 "follower" => user_to_map(follower),
287 "message" => ""
288 }
289 }
290 |> insert_log_entry_with_message()
291 end
292
293 @spec insert_log(%{
294 actor: User,
295 action: String.t(),
296 nicknames: [String.t()],
297 tags: [String.t()]
298 }) :: {:ok, ModerationLog} | {:error, any}
299 def insert_log(%{
300 actor: %User{} = actor,
301 nicknames: nicknames,
302 tags: tags,
303 action: action
304 }) do
305 %ModerationLog{
306 data: %{
307 "actor" => user_to_map(actor),
308 "nicknames" => nicknames,
309 "tags" => tags,
310 "action" => action,
311 "message" => ""
312 }
313 }
314 |> insert_log_entry_with_message()
315 end
316
317 @spec insert_log(%{actor: User, action: String.t(), target: String.t()}) ::
318 {:ok, ModerationLog} | {:error, any}
319 def insert_log(%{
320 actor: %User{} = actor,
321 action: action,
322 target: target
323 })
324 when action in ["relay_follow", "relay_unfollow"] do
325 %ModerationLog{
326 data: %{
327 "actor" => user_to_map(actor),
328 "action" => action,
329 "target" => target,
330 "message" => ""
331 }
332 }
333 |> insert_log_entry_with_message()
334 end
335
336 @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) ::
337 {:ok, ModerationLog} | {:error, any}
338 def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do
339 %ModerationLog{
340 data: %{
341 "actor" => %{"nickname" => actor.nickname},
342 "action" => "chat_message_delete",
343 "subject_id" => subject_id
344 }
345 }
346 |> insert_log_entry_with_message()
347 end
348
349 @spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any}
350 defp insert_log_entry_with_message(entry) do
351 entry.data["message"]
352 |> put_in(get_log_entry_message(entry))
353 |> Repo.insert()
354 end
355
356 defp user_to_map(users) when is_list(users) do
357 Enum.map(users, &user_to_map/1)
358 end
359
360 defp user_to_map(%User{} = user) do
361 user
362 |> Map.take([:id, :nickname])
363 |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
364 |> Map.put("type", "user")
365 end
366
367 defp user_to_map(_), do: nil
368
369 defp report_to_map(%Activity{} = report) do
370 %{
371 "type" => "report",
372 "id" => report.id,
373 "state" => report.data["state"]
374 }
375 end
376
377 defp status_to_map(%Activity{} = status) do
378 %{
379 "type" => "status",
380 "id" => status.id
381 }
382 end
383
384 def get_log_entry_message(%ModerationLog{
385 data: %{
386 "actor" => %{"nickname" => actor_nickname},
387 "action" => action,
388 "followed" => %{"nickname" => followed_nickname},
389 "follower" => %{"nickname" => follower_nickname}
390 }
391 }) do
392 "@#{actor_nickname} made @#{follower_nickname} #{action} @#{followed_nickname}"
393 end
394
395 @spec get_log_entry_message(ModerationLog) :: String.t()
396 def get_log_entry_message(%ModerationLog{
397 data: %{
398 "actor" => %{"nickname" => actor_nickname},
399 "action" => "delete",
400 "subject" => subjects
401 }
402 }) do
403 "@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}"
404 end
405
406 @spec get_log_entry_message(ModerationLog) :: String.t()
407 def get_log_entry_message(%ModerationLog{
408 data: %{
409 "actor" => %{"nickname" => actor_nickname},
410 "action" => "create",
411 "subjects" => subjects
412 }
413 }) do
414 "@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}"
415 end
416
417 @spec get_log_entry_message(ModerationLog) :: String.t()
418 def get_log_entry_message(%ModerationLog{
419 data: %{
420 "actor" => %{"nickname" => actor_nickname},
421 "action" => "activate",
422 "subject" => users
423 }
424 }) do
425 "@#{actor_nickname} activated users: #{users_to_nicknames_string(users)}"
426 end
427
428 @spec get_log_entry_message(ModerationLog) :: String.t()
429 def get_log_entry_message(%ModerationLog{
430 data: %{
431 "actor" => %{"nickname" => actor_nickname},
432 "action" => "deactivate",
433 "subject" => users
434 }
435 }) do
436 "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}"
437 end
438
439 @spec get_log_entry_message(ModerationLog) :: String.t()
440 def get_log_entry_message(%ModerationLog{
441 data: %{
442 "actor" => %{"nickname" => actor_nickname},
443 "action" => "approve",
444 "subject" => users
445 }
446 }) do
447 "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
448 end
449
450 @spec get_log_entry_message(ModerationLog) :: String.t()
451 def get_log_entry_message(%ModerationLog{
452 data: %{
453 "actor" => %{"nickname" => actor_nickname},
454 "nicknames" => nicknames,
455 "tags" => tags,
456 "action" => "tag"
457 }
458 }) do
459 tags_string = tags |> Enum.join(", ")
460
461 "@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}"
462 end
463
464 @spec get_log_entry_message(ModerationLog) :: String.t()
465 def get_log_entry_message(%ModerationLog{
466 data: %{
467 "actor" => %{"nickname" => actor_nickname},
468 "nicknames" => nicknames,
469 "tags" => tags,
470 "action" => "untag"
471 }
472 }) do
473 tags_string = tags |> Enum.join(", ")
474
475 "@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}"
476 end
477
478 @spec get_log_entry_message(ModerationLog) :: String.t()
479 def get_log_entry_message(%ModerationLog{
480 data: %{
481 "actor" => %{"nickname" => actor_nickname},
482 "action" => "grant",
483 "subject" => users,
484 "permission" => permission
485 }
486 }) do
487 "@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}"
488 end
489
490 @spec get_log_entry_message(ModerationLog) :: String.t()
491 def get_log_entry_message(%ModerationLog{
492 data: %{
493 "actor" => %{"nickname" => actor_nickname},
494 "action" => "revoke",
495 "subject" => users,
496 "permission" => permission
497 }
498 }) do
499 "@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}"
500 end
501
502 @spec get_log_entry_message(ModerationLog) :: String.t()
503 def get_log_entry_message(%ModerationLog{
504 data: %{
505 "actor" => %{"nickname" => actor_nickname},
506 "action" => "relay_follow",
507 "target" => target
508 }
509 }) do
510 "@#{actor_nickname} followed relay: #{target}"
511 end
512
513 @spec get_log_entry_message(ModerationLog) :: String.t()
514 def get_log_entry_message(%ModerationLog{
515 data: %{
516 "actor" => %{"nickname" => actor_nickname},
517 "action" => "relay_unfollow",
518 "target" => target
519 }
520 }) do
521 "@#{actor_nickname} unfollowed relay: #{target}"
522 end
523
524 @spec get_log_entry_message(ModerationLog) :: String.t()
525 def get_log_entry_message(
526 %ModerationLog{
527 data: %{
528 "actor" => %{"nickname" => actor_nickname},
529 "action" => "report_update",
530 "subject" => %{"id" => subject_id, "state" => state, "type" => "report"}
531 }
532 } = log
533 ) do
534 "@#{actor_nickname} updated report ##{subject_id}" <>
535 subject_actor_nickname(log, " (on user ", ")") <>
536 " with '#{state}' state"
537 end
538
539 @spec get_log_entry_message(ModerationLog) :: String.t()
540 def get_log_entry_message(
541 %ModerationLog{
542 data: %{
543 "actor" => %{"nickname" => actor_nickname},
544 "action" => "report_note",
545 "subject" => %{"id" => subject_id, "type" => "report"},
546 "text" => text
547 }
548 } = log
549 ) do
550 "@#{actor_nickname} added note '#{text}' to report ##{subject_id}" <>
551 subject_actor_nickname(log, " on user ")
552 end
553
554 @spec get_log_entry_message(ModerationLog) :: String.t()
555 def get_log_entry_message(
556 %ModerationLog{
557 data: %{
558 "actor" => %{"nickname" => actor_nickname},
559 "action" => "report_note_delete",
560 "subject" => %{"id" => subject_id, "type" => "report"},
561 "text" => text
562 }
563 } = log
564 ) do
565 "@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}" <>
566 subject_actor_nickname(log, " on user ")
567 end
568
569 @spec get_log_entry_message(ModerationLog) :: String.t()
570 def get_log_entry_message(%ModerationLog{
571 data: %{
572 "actor" => %{"nickname" => actor_nickname},
573 "action" => "status_update",
574 "subject" => %{"id" => subject_id, "type" => "status"},
575 "sensitive" => nil,
576 "visibility" => visibility
577 }
578 }) do
579 "@#{actor_nickname} updated status ##{subject_id}, set visibility: '#{visibility}'"
580 end
581
582 @spec get_log_entry_message(ModerationLog) :: String.t()
583 def get_log_entry_message(%ModerationLog{
584 data: %{
585 "actor" => %{"nickname" => actor_nickname},
586 "action" => "status_update",
587 "subject" => %{"id" => subject_id, "type" => "status"},
588 "sensitive" => sensitive,
589 "visibility" => nil
590 }
591 }) do
592 "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}'"
593 end
594
595 @spec get_log_entry_message(ModerationLog) :: String.t()
596 def get_log_entry_message(%ModerationLog{
597 data: %{
598 "actor" => %{"nickname" => actor_nickname},
599 "action" => "status_update",
600 "subject" => %{"id" => subject_id, "type" => "status"},
601 "sensitive" => sensitive,
602 "visibility" => visibility
603 }
604 }) do
605 "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{
606 visibility
607 }'"
608 end
609
610 @spec get_log_entry_message(ModerationLog) :: String.t()
611 def get_log_entry_message(%ModerationLog{
612 data: %{
613 "actor" => %{"nickname" => actor_nickname},
614 "action" => "status_delete",
615 "subject_id" => subject_id
616 }
617 }) do
618 "@#{actor_nickname} deleted status ##{subject_id}"
619 end
620
621 @spec get_log_entry_message(ModerationLog) :: String.t()
622 def get_log_entry_message(%ModerationLog{
623 data: %{
624 "actor" => %{"nickname" => actor_nickname},
625 "action" => "force_password_reset",
626 "subject" => subjects
627 }
628 }) do
629 "@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
630 end
631
632 @spec get_log_entry_message(ModerationLog) :: String.t()
633 def get_log_entry_message(%ModerationLog{
634 data: %{
635 "actor" => %{"nickname" => actor_nickname},
636 "action" => "confirm_email",
637 "subject" => subjects
638 }
639 }) do
640 "@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
641 end
642
643 @spec get_log_entry_message(ModerationLog) :: String.t()
644 def get_log_entry_message(%ModerationLog{
645 data: %{
646 "actor" => %{"nickname" => actor_nickname},
647 "action" => "resend_confirmation_email",
648 "subject" => subjects
649 }
650 }) do
651 "@#{actor_nickname} re-sent confirmation email for users: #{
652 users_to_nicknames_string(subjects)
653 }"
654 end
655
656 @spec get_log_entry_message(ModerationLog) :: String.t()
657 def get_log_entry_message(%ModerationLog{
658 data: %{
659 "actor" => %{"nickname" => actor_nickname},
660 "action" => "updated_users",
661 "subject" => subjects
662 }
663 }) do
664 "@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}"
665 end
666
667 @spec get_log_entry_message(ModerationLog) :: String.t()
668 def get_log_entry_message(%ModerationLog{
669 data: %{
670 "actor" => %{"nickname" => actor_nickname},
671 "action" => "chat_message_delete",
672 "subject_id" => subject_id
673 }
674 }) do
675 "@#{actor_nickname} deleted chat message ##{subject_id}"
676 end
677
678 def get_log_entry_message(%ModerationLog{
679 data: %{
680 "actor" => %{"nickname" => actor_nickname},
681 "action" => "create_backup",
682 "subject" => %{"nickname" => user_nickname}
683 }
684 }) do
685 "@#{actor_nickname} requested account backup for @#{user_nickname}"
686 end
687
688 defp nicknames_to_string(nicknames) do
689 nicknames
690 |> Enum.map(&"@#{&1}")
691 |> Enum.join(", ")
692 end
693
694 defp users_to_nicknames_string(users) do
695 users
696 |> Enum.map(&"@#{&1["nickname"]}")
697 |> Enum.join(", ")
698 end
699
700 defp subject_actor_nickname(%ModerationLog{data: data}, prefix_msg, postfix_msg \\ "") do
701 case data do
702 %{"subject_actor" => %{"nickname" => subject_actor}} ->
703 [prefix_msg, "@#{subject_actor}", postfix_msg]
704 |> Enum.reject(&(&1 == ""))
705 |> Enum.join()
706
707 _ ->
708 ""
709 end
710 end
711 end