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