activitypub: add support for forwarding reports
[akkoma] / lib / pleroma / web / activity_pub / activity_pub.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.ActivityPub do
6 alias Pleroma.Activity
7 alias Pleroma.Instances
8 alias Pleroma.Notification
9 alias Pleroma.Object
10 alias Pleroma.Repo
11 alias Pleroma.Upload
12 alias Pleroma.User
13 alias Pleroma.Web.ActivityPub.MRF
14 alias Pleroma.Web.ActivityPub.Transmogrifier
15 alias Pleroma.Web.Federator
16 alias Pleroma.Web.OStatus
17 alias Pleroma.Web.WebFinger
18
19 import Ecto.Query
20 import Pleroma.Web.ActivityPub.Utils
21 import Pleroma.Web.ActivityPub.Visibility
22
23 require Logger
24
25 @httpoison Application.get_env(:pleroma, :httpoison)
26
27 # For Announce activities, we filter the recipients based on following status for any actors
28 # that match actual users. See issue #164 for more information about why this is necessary.
29 defp get_recipients(%{"type" => "Announce"} = data) do
30 to = data["to"] || []
31 cc = data["cc"] || []
32 actor = User.get_cached_by_ap_id(data["actor"])
33
34 recipients =
35 (to ++ cc)
36 |> Enum.filter(fn recipient ->
37 case User.get_cached_by_ap_id(recipient) do
38 nil ->
39 true
40
41 user ->
42 User.following?(user, actor)
43 end
44 end)
45
46 {recipients, to, cc}
47 end
48
49 defp get_recipients(%{"type" => "Create"} = data) do
50 to = data["to"] || []
51 cc = data["cc"] || []
52 actor = data["actor"] || []
53 recipients = (to ++ cc ++ [actor]) |> Enum.uniq()
54 {recipients, to, cc}
55 end
56
57 defp get_recipients(data) do
58 to = data["to"] || []
59 cc = data["cc"] || []
60 recipients = to ++ cc
61 {recipients, to, cc}
62 end
63
64 defp check_actor_is_active(actor) do
65 if not is_nil(actor) do
66 with user <- User.get_cached_by_ap_id(actor),
67 false <- user.info.deactivated do
68 :ok
69 else
70 _e -> :reject
71 end
72 else
73 :ok
74 end
75 end
76
77 defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
78 limit = Pleroma.Config.get([:instance, :remote_limit])
79 String.length(content) <= limit
80 end
81
82 defp check_remote_limit(_), do: true
83
84 def increase_note_count_if_public(actor, object) do
85 if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
86 end
87
88 def decrease_note_count_if_public(actor, object) do
89 if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
90 end
91
92 def insert(map, local \\ true) when is_map(map) do
93 with nil <- Activity.normalize(map),
94 map <- lazy_put_activity_defaults(map),
95 :ok <- check_actor_is_active(map["actor"]),
96 {_, true} <- {:remote_limit_error, check_remote_limit(map)},
97 {:ok, map} <- MRF.filter(map),
98 :ok <- insert_full_object(map) do
99 {recipients, _, _} = get_recipients(map)
100
101 {:ok, activity} =
102 Repo.insert(%Activity{
103 data: map,
104 local: local,
105 actor: map["actor"],
106 recipients: recipients
107 })
108
109 Task.start(fn ->
110 Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
111 end)
112
113 Notification.create_notifications(activity)
114 stream_out(activity)
115 {:ok, activity}
116 else
117 %Activity{} = activity -> {:ok, activity}
118 error -> {:error, error}
119 end
120 end
121
122 def stream_out(activity) do
123 public = "https://www.w3.org/ns/activitystreams#Public"
124
125 if activity.data["type"] in ["Create", "Announce", "Delete"] do
126 Pleroma.Web.Streamer.stream("user", activity)
127 Pleroma.Web.Streamer.stream("list", activity)
128
129 if Enum.member?(activity.data["to"], public) do
130 Pleroma.Web.Streamer.stream("public", activity)
131
132 if activity.local do
133 Pleroma.Web.Streamer.stream("public:local", activity)
134 end
135
136 if activity.data["type"] in ["Create"] do
137 activity.data["object"]
138 |> Map.get("tag", [])
139 |> Enum.filter(fn tag -> is_bitstring(tag) end)
140 |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)
141
142 if activity.data["object"]["attachment"] != [] do
143 Pleroma.Web.Streamer.stream("public:media", activity)
144
145 if activity.local do
146 Pleroma.Web.Streamer.stream("public:local:media", activity)
147 end
148 end
149 end
150 else
151 if !Enum.member?(activity.data["cc"] || [], public) &&
152 !Enum.member?(
153 activity.data["to"],
154 User.get_by_ap_id(activity.data["actor"]).follower_address
155 ),
156 do: Pleroma.Web.Streamer.stream("direct", activity)
157 end
158 end
159 end
160
161 def create(%{to: to, actor: actor, context: context, object: object} = params) do
162 additional = params[:additional] || %{}
163 # only accept false as false value
164 local = !(params[:local] == false)
165 published = params[:published]
166
167 with create_data <-
168 make_create_data(
169 %{to: to, actor: actor, published: published, context: context, object: object},
170 additional
171 ),
172 {:ok, activity} <- insert(create_data, local),
173 # Changing note count prior to enqueuing federation task in order to avoid
174 # race conditions on updating user.info
175 {:ok, _actor} <- increase_note_count_if_public(actor, activity),
176 :ok <- maybe_federate(activity) do
177 {:ok, activity}
178 end
179 end
180
181 def accept(%{to: to, actor: actor, object: object} = params) do
182 # only accept false as false value
183 local = !(params[:local] == false)
184
185 with data <- %{"to" => to, "type" => "Accept", "actor" => actor.ap_id, "object" => object},
186 {:ok, activity} <- insert(data, local),
187 :ok <- maybe_federate(activity) do
188 {:ok, activity}
189 end
190 end
191
192 def reject(%{to: to, actor: actor, object: object} = params) do
193 # only accept false as false value
194 local = !(params[:local] == false)
195
196 with data <- %{"to" => to, "type" => "Reject", "actor" => actor.ap_id, "object" => object},
197 {:ok, activity} <- insert(data, local),
198 :ok <- maybe_federate(activity) do
199 {:ok, activity}
200 end
201 end
202
203 def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
204 # only accept false as false value
205 local = !(params[:local] == false)
206
207 with data <- %{
208 "to" => to,
209 "cc" => cc,
210 "type" => "Update",
211 "actor" => actor,
212 "object" => object
213 },
214 {:ok, activity} <- insert(data, local),
215 :ok <- maybe_federate(activity) do
216 {:ok, activity}
217 end
218 end
219
220 # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
221 def like(
222 %User{ap_id: ap_id} = user,
223 %Object{data: %{"id" => _}} = object,
224 activity_id \\ nil,
225 local \\ true
226 ) do
227 with nil <- get_existing_like(ap_id, object),
228 like_data <- make_like_data(user, object, activity_id),
229 {:ok, activity} <- insert(like_data, local),
230 {:ok, object} <- add_like_to_object(activity, object),
231 :ok <- maybe_federate(activity) do
232 {:ok, activity, object}
233 else
234 %Activity{} = activity -> {:ok, activity, object}
235 error -> {:error, error}
236 end
237 end
238
239 def unlike(
240 %User{} = actor,
241 %Object{} = object,
242 activity_id \\ nil,
243 local \\ true
244 ) do
245 with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
246 unlike_data <- make_unlike_data(actor, like_activity, activity_id),
247 {:ok, unlike_activity} <- insert(unlike_data, local),
248 {:ok, _activity} <- Repo.delete(like_activity),
249 {:ok, object} <- remove_like_from_object(like_activity, object),
250 :ok <- maybe_federate(unlike_activity) do
251 {:ok, unlike_activity, like_activity, object}
252 else
253 _e -> {:ok, object}
254 end
255 end
256
257 def announce(
258 %User{ap_id: _} = user,
259 %Object{data: %{"id" => _}} = object,
260 activity_id \\ nil,
261 local \\ true,
262 public \\ true
263 ) do
264 with true <- is_public?(object),
265 announce_data <- make_announce_data(user, object, activity_id, public),
266 {:ok, activity} <- insert(announce_data, local),
267 {:ok, object} <- add_announce_to_object(activity, object),
268 :ok <- maybe_federate(activity) do
269 {:ok, activity, object}
270 else
271 error -> {:error, error}
272 end
273 end
274
275 def unannounce(
276 %User{} = actor,
277 %Object{} = object,
278 activity_id \\ nil,
279 local \\ true
280 ) do
281 with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
282 unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
283 {:ok, unannounce_activity} <- insert(unannounce_data, local),
284 :ok <- maybe_federate(unannounce_activity),
285 {:ok, _activity} <- Repo.delete(announce_activity),
286 {:ok, object} <- remove_announce_from_object(announce_activity, object) do
287 {:ok, unannounce_activity, object}
288 else
289 _e -> {:ok, object}
290 end
291 end
292
293 def follow(follower, followed, activity_id \\ nil, local \\ true) do
294 with data <- make_follow_data(follower, followed, activity_id),
295 {:ok, activity} <- insert(data, local),
296 :ok <- maybe_federate(activity) do
297 {:ok, activity}
298 end
299 end
300
301 def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
302 with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
303 {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
304 unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
305 {:ok, activity} <- insert(unfollow_data, local),
306 :ok <- maybe_federate(activity) do
307 {:ok, activity}
308 end
309 end
310
311 def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
312 user = User.get_cached_by_ap_id(actor)
313 to = (object.data["to"] || []) ++ (object.data["cc"] || [])
314
315 with {:ok, object, activity} <- Object.delete(object),
316 data <- %{
317 "type" => "Delete",
318 "actor" => actor,
319 "object" => id,
320 "to" => to,
321 "deleted_activity_id" => activity && activity.id
322 },
323 {:ok, activity} <- insert(data, local),
324 # Changing note count prior to enqueuing federation task in order to avoid
325 # race conditions on updating user.info
326 {:ok, _actor} <- decrease_note_count_if_public(user, object),
327 :ok <- maybe_federate(activity) do
328 {:ok, activity}
329 end
330 end
331
332 def block(blocker, blocked, activity_id \\ nil, local \\ true) do
333 ap_config = Application.get_env(:pleroma, :activitypub)
334 unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked)
335 outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks)
336
337 with true <- unfollow_blocked do
338 follow_activity = fetch_latest_follow(blocker, blocked)
339
340 if follow_activity do
341 unfollow(blocker, blocked, nil, local)
342 end
343 end
344
345 with true <- outgoing_blocks,
346 block_data <- make_block_data(blocker, blocked, activity_id),
347 {:ok, activity} <- insert(block_data, local),
348 :ok <- maybe_federate(activity) do
349 {:ok, activity}
350 else
351 _e -> {:ok, nil}
352 end
353 end
354
355 def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
356 with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
357 unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
358 {:ok, activity} <- insert(unblock_data, local),
359 :ok <- maybe_federate(activity) do
360 {:ok, activity}
361 end
362 end
363
364 def flag(
365 %{
366 actor: actor,
367 context: context,
368 account: account,
369 statuses: statuses,
370 content: content
371 } = params
372 ) do
373 # only accept false as false value
374 local = !(params[:local] == false)
375 forward = !(params[:forward] == false)
376
377 additional = params[:additional] || %{}
378
379 params = %{
380 actor: actor,
381 context: context,
382 account: account,
383 statuses: statuses,
384 content: content
385 }
386
387 additional =
388 if forward do
389 Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]})
390 else
391 additional
392 end
393
394 with flag_data <- make_flag_data(params, additional),
395 {:ok, activity} <- insert(flag_data, local),
396 :ok <- maybe_federate(activity) do
397 {:ok, activity}
398 end
399 end
400
401 def fetch_activities_for_context(context, opts \\ %{}) do
402 public = ["https://www.w3.org/ns/activitystreams#Public"]
403
404 recipients =
405 if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
406
407 query = from(activity in Activity)
408
409 query =
410 query
411 |> restrict_blocked(opts)
412 |> restrict_recipients(recipients, opts["user"])
413
414 query =
415 from(
416 activity in query,
417 where:
418 fragment(
419 "?->>'type' = ? and ?->>'context' = ?",
420 activity.data,
421 "Create",
422 activity.data,
423 ^context
424 ),
425 order_by: [desc: :id]
426 )
427
428 Repo.all(query)
429 end
430
431 def fetch_public_activities(opts \\ %{}) do
432 q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
433
434 q
435 |> restrict_unlisted()
436 |> Repo.all()
437 |> Enum.reverse()
438 end
439
440 @valid_visibilities ~w[direct unlisted public private]
441
442 defp restrict_visibility(query, %{visibility: visibility})
443 when is_list(visibility) do
444 if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
445 query =
446 from(
447 a in query,
448 where:
449 fragment(
450 "activity_visibility(?, ?, ?) = ANY (?)",
451 a.actor,
452 a.recipients,
453 a.data,
454 ^visibility
455 )
456 )
457
458 Ecto.Adapters.SQL.to_sql(:all, Repo, query)
459
460 query
461 else
462 Logger.error("Could not restrict visibility to #{visibility}")
463 end
464 end
465
466 defp restrict_visibility(query, %{visibility: visibility})
467 when visibility in @valid_visibilities do
468 query =
469 from(
470 a in query,
471 where:
472 fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
473 )
474
475 Ecto.Adapters.SQL.to_sql(:all, Repo, query)
476
477 query
478 end
479
480 defp restrict_visibility(_query, %{visibility: visibility})
481 when visibility not in @valid_visibilities do
482 Logger.error("Could not restrict visibility to #{visibility}")
483 end
484
485 defp restrict_visibility(query, _visibility), do: query
486
487 def fetch_user_activities(user, reading_user, params \\ %{}) do
488 params =
489 params
490 |> Map.put("type", ["Create", "Announce"])
491 |> Map.put("actor_id", user.ap_id)
492 |> Map.put("whole_db", true)
493 |> Map.put("pinned_activity_ids", user.info.pinned_activities)
494
495 recipients =
496 if reading_user do
497 ["https://www.w3.org/ns/activitystreams#Public"] ++
498 [reading_user.ap_id | reading_user.following]
499 else
500 ["https://www.w3.org/ns/activitystreams#Public"]
501 end
502
503 fetch_activities(recipients, params)
504 |> Enum.reverse()
505 end
506
507 defp restrict_since(query, %{"since_id" => ""}), do: query
508
509 defp restrict_since(query, %{"since_id" => since_id}) do
510 from(activity in query, where: activity.id > ^since_id)
511 end
512
513 defp restrict_since(query, _), do: query
514
515 defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})
516 when is_list(tag_reject) and tag_reject != [] do
517 from(
518 activity in query,
519 where: fragment(~s(\(not \(? #> '{"object","tag"}'\) \\?| ?\)), activity.data, ^tag_reject)
520 )
521 end
522
523 defp restrict_tag_reject(query, _), do: query
524
525 defp restrict_tag_all(query, %{"tag_all" => tag_all})
526 when is_list(tag_all) and tag_all != [] do
527 from(
528 activity in query,
529 where: fragment(~s(\(? #> '{"object","tag"}'\) \\?& ?), activity.data, ^tag_all)
530 )
531 end
532
533 defp restrict_tag_all(query, _), do: query
534
535 defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
536 from(
537 activity in query,
538 where: fragment(~s(\(? #> '{"object","tag"}'\) \\?| ?), activity.data, ^tag)
539 )
540 end
541
542 defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
543 from(
544 activity in query,
545 where: fragment(~s(? <@ (? #> '{"object","tag"}'\)), ^tag, activity.data)
546 )
547 end
548
549 defp restrict_tag(query, _), do: query
550
551 defp restrict_to_cc(query, recipients_to, recipients_cc) do
552 from(
553 activity in query,
554 where:
555 fragment(
556 "(?->'to' \\?| ?) or (?->'cc' \\?| ?)",
557 activity.data,
558 ^recipients_to,
559 activity.data,
560 ^recipients_cc
561 )
562 )
563 end
564
565 defp restrict_recipients(query, [], _user), do: query
566
567 defp restrict_recipients(query, recipients, nil) do
568 from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
569 end
570
571 defp restrict_recipients(query, recipients, user) do
572 from(
573 activity in query,
574 where: fragment("? && ?", ^recipients, activity.recipients),
575 or_where: activity.actor == ^user.ap_id
576 )
577 end
578
579 defp restrict_limit(query, %{"limit" => limit}) do
580 from(activity in query, limit: ^limit)
581 end
582
583 defp restrict_limit(query, _), do: query
584
585 defp restrict_local(query, %{"local_only" => true}) do
586 from(activity in query, where: activity.local == true)
587 end
588
589 defp restrict_local(query, _), do: query
590
591 defp restrict_max(query, %{"max_id" => ""}), do: query
592
593 defp restrict_max(query, %{"max_id" => max_id}) do
594 from(activity in query, where: activity.id < ^max_id)
595 end
596
597 defp restrict_max(query, _), do: query
598
599 defp restrict_actor(query, %{"actor_id" => actor_id}) do
600 from(activity in query, where: activity.actor == ^actor_id)
601 end
602
603 defp restrict_actor(query, _), do: query
604
605 defp restrict_type(query, %{"type" => type}) when is_binary(type) do
606 from(activity in query, where: fragment("?->>'type' = ?", activity.data, ^type))
607 end
608
609 defp restrict_type(query, %{"type" => type}) do
610 from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
611 end
612
613 defp restrict_type(query, _), do: query
614
615 defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
616 from(
617 activity in query,
618 where: fragment(~s(? <@ (? #> '{"object","likes"}'\)), ^ap_id, activity.data)
619 )
620 end
621
622 defp restrict_favorited_by(query, _), do: query
623
624 defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
625 from(
626 activity in query,
627 where: fragment(~s(not (? #> '{"object","attachment"}' = ?\)), activity.data, ^[])
628 )
629 end
630
631 defp restrict_media(query, _), do: query
632
633 defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
634 from(
635 activity in query,
636 where: fragment("?->'object'->>'inReplyTo' is null", activity.data)
637 )
638 end
639
640 defp restrict_replies(query, _), do: query
641
642 defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do
643 from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
644 end
645
646 defp restrict_reblogs(query, _), do: query
647
648 defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
649
650 defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
651 mutes = info.mutes
652
653 from(
654 activity in query,
655 where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
656 where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
657 )
658 end
659
660 defp restrict_muted(query, _), do: query
661
662 defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
663 blocks = info.blocks || []
664 domain_blocks = info.domain_blocks || []
665
666 from(
667 activity in query,
668 where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
669 where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks),
670 where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
671 )
672 end
673
674 defp restrict_blocked(query, _), do: query
675
676 defp restrict_unlisted(query) do
677 from(
678 activity in query,
679 where:
680 fragment(
681 "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
682 activity.data,
683 ^["https://www.w3.org/ns/activitystreams#Public"]
684 )
685 )
686 end
687
688 defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
689 from(activity in query, where: activity.id in ^ids)
690 end
691
692 defp restrict_pinned(query, _), do: query
693
694 def fetch_activities_query(recipients, opts \\ %{}) do
695 base_query =
696 from(
697 activity in Activity,
698 limit: 20,
699 order_by: [fragment("? desc nulls last", activity.id)]
700 )
701
702 base_query
703 |> restrict_recipients(recipients, opts["user"])
704 |> restrict_tag(opts)
705 |> restrict_tag_reject(opts)
706 |> restrict_tag_all(opts)
707 |> restrict_since(opts)
708 |> restrict_local(opts)
709 |> restrict_limit(opts)
710 |> restrict_max(opts)
711 |> restrict_actor(opts)
712 |> restrict_type(opts)
713 |> restrict_favorited_by(opts)
714 |> restrict_blocked(opts)
715 |> restrict_muted(opts)
716 |> restrict_media(opts)
717 |> restrict_visibility(opts)
718 |> restrict_replies(opts)
719 |> restrict_reblogs(opts)
720 |> restrict_pinned(opts)
721 end
722
723 def fetch_activities(recipients, opts \\ %{}) do
724 fetch_activities_query(recipients, opts)
725 |> Repo.all()
726 |> Enum.reverse()
727 end
728
729 def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do
730 fetch_activities_query([], opts)
731 |> restrict_to_cc(recipients_to, recipients_cc)
732 |> Repo.all()
733 |> Enum.reverse()
734 end
735
736 def upload(file, opts \\ []) do
737 with {:ok, data} <- Upload.store(file, opts) do
738 obj_data =
739 if opts[:actor] do
740 Map.put(data, "actor", opts[:actor])
741 else
742 data
743 end
744
745 Repo.insert(%Object{data: obj_data})
746 end
747 end
748
749 def user_data_from_user_object(data) do
750 avatar =
751 data["icon"]["url"] &&
752 %{
753 "type" => "Image",
754 "url" => [%{"href" => data["icon"]["url"]}]
755 }
756
757 banner =
758 data["image"]["url"] &&
759 %{
760 "type" => "Image",
761 "url" => [%{"href" => data["image"]["url"]}]
762 }
763
764 locked = data["manuallyApprovesFollowers"] || false
765 data = Transmogrifier.maybe_fix_user_object(data)
766
767 user_data = %{
768 ap_id: data["id"],
769 info: %{
770 "ap_enabled" => true,
771 "source_data" => data,
772 "banner" => banner,
773 "locked" => locked
774 },
775 avatar: avatar,
776 name: data["name"],
777 follower_address: data["followers"],
778 bio: data["summary"]
779 }
780
781 # nickname can be nil because of virtual actors
782 user_data =
783 if data["preferredUsername"] do
784 Map.put(
785 user_data,
786 :nickname,
787 "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
788 )
789 else
790 Map.put(user_data, :nickname, nil)
791 end
792
793 {:ok, user_data}
794 end
795
796 def fetch_and_prepare_user_from_ap_id(ap_id) do
797 with {:ok, data} <- fetch_and_contain_remote_object_from_id(ap_id) do
798 user_data_from_user_object(data)
799 else
800 e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
801 end
802 end
803
804 def make_user_from_ap_id(ap_id) do
805 if _user = User.get_by_ap_id(ap_id) do
806 Transmogrifier.upgrade_user_from_ap_id(ap_id)
807 else
808 with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
809 User.insert_or_update_user(data)
810 else
811 e -> {:error, e}
812 end
813 end
814 end
815
816 def make_user_from_nickname(nickname) do
817 with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
818 make_user_from_ap_id(ap_id)
819 else
820 _e -> {:error, "No AP id in WebFinger"}
821 end
822 end
823
824 def should_federate?(inbox, public) do
825 if public do
826 true
827 else
828 inbox_info = URI.parse(inbox)
829 !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
830 end
831 end
832
833 def publish(actor, activity) do
834 remote_followers =
835 if actor.follower_address in activity.recipients do
836 {:ok, followers} = User.get_followers(actor)
837 followers |> Enum.filter(&(!&1.local))
838 else
839 []
840 end
841
842 public = is_public?(activity)
843
844 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
845 json = Jason.encode!(data)
846
847 (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
848 |> Enum.filter(fn user -> User.ap_enabled?(user) end)
849 |> Enum.map(fn %{info: %{source_data: data}} ->
850 (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
851 end)
852 |> Enum.uniq()
853 |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
854 |> Instances.filter_reachable()
855 |> Enum.each(fn {inbox, unreachable_since} ->
856 Federator.publish_single_ap(%{
857 inbox: inbox,
858 json: json,
859 actor: actor,
860 id: activity.data["id"],
861 unreachable_since: unreachable_since
862 })
863 end)
864 end
865
866 def publish_one(%{inbox: inbox, json: json, actor: actor, id: id} = params) do
867 Logger.info("Federating #{id} to #{inbox}")
868 host = URI.parse(inbox).host
869
870 digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
871
872 date =
873 NaiveDateTime.utc_now()
874 |> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
875
876 signature =
877 Pleroma.Web.HTTPSignatures.sign(actor, %{
878 host: host,
879 "content-length": byte_size(json),
880 digest: digest,
881 date: date
882 })
883
884 with {:ok, %{status: code}} when code in 200..299 <-
885 result =
886 @httpoison.post(
887 inbox,
888 json,
889 [
890 {"Content-Type", "application/activity+json"},
891 {"Date", date},
892 {"signature", signature},
893 {"digest", digest}
894 ]
895 ) do
896 if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
897 do: Instances.set_reachable(inbox)
898
899 result
900 else
901 {_post_result, response} ->
902 unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
903 {:error, response}
904 end
905 end
906
907 # TODO:
908 # This will create a Create activity, which we need internally at the moment.
909 def fetch_object_from_id(id) do
910 if object = Object.get_cached_by_ap_id(id) do
911 {:ok, object}
912 else
913 with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
914 nil <- Object.normalize(data),
915 params <- %{
916 "type" => "Create",
917 "to" => data["to"],
918 "cc" => data["cc"],
919 "actor" => data["actor"] || data["attributedTo"],
920 "object" => data
921 },
922 :ok <- Transmogrifier.contain_origin(id, params),
923 {:ok, activity} <- Transmogrifier.handle_incoming(params) do
924 {:ok, Object.normalize(activity.data["object"])}
925 else
926 {:error, {:reject, nil}} ->
927 {:reject, nil}
928
929 object = %Object{} ->
930 {:ok, object}
931
932 _e ->
933 Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
934
935 case OStatus.fetch_activity_from_url(id) do
936 {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
937 e -> e
938 end
939 end
940 end
941 end
942
943 def fetch_and_contain_remote_object_from_id(id) do
944 Logger.info("Fetching object #{id} via AP")
945
946 with true <- String.starts_with?(id, "http"),
947 {:ok, %{body: body, status: code}} when code in 200..299 <-
948 @httpoison.get(
949 id,
950 [{:Accept, "application/activity+json"}]
951 ),
952 {:ok, data} <- Jason.decode(body),
953 :ok <- Transmogrifier.contain_origin_from_id(id, data) do
954 {:ok, data}
955 else
956 e ->
957 {:error, e}
958 end
959 end
960
961 # filter out broken threads
962 def contain_broken_threads(%Activity{} = activity, %User{} = user) do
963 entire_thread_visible_for_user?(activity, user)
964 end
965
966 # do post-processing on a specific activity
967 def contain_activity(%Activity{} = activity, %User{} = user) do
968 contain_broken_threads(activity, user)
969 end
970
971 # do post-processing on a timeline
972 def contain_timeline(timeline, user) do
973 timeline
974 |> Enum.filter(fn activity ->
975 contain_activity(activity, user)
976 end)
977 end
978 end