Merge branch 'feature/reports' into 'develop'
[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 data = %{
309 "type" => "Delete",
310 "actor" => actor,
311 "object" => id,
312 "to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
313 }
314
315 with {:ok, _} <- Object.delete(object),
316 {:ok, activity} <- insert(data, local),
317 # Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info
318 {:ok, _actor} <- User.decrease_note_count(user),
319 :ok <- maybe_federate(activity) do
320 {:ok, activity}
321 end
322 end
323
324 def block(blocker, blocked, activity_id \\ nil, local \\ true) do
325 ap_config = Application.get_env(:pleroma, :activitypub)
326 unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked)
327 outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks)
328
329 with true <- unfollow_blocked do
330 follow_activity = fetch_latest_follow(blocker, blocked)
331
332 if follow_activity do
333 unfollow(blocker, blocked, nil, local)
334 end
335 end
336
337 with true <- outgoing_blocks,
338 block_data <- make_block_data(blocker, blocked, activity_id),
339 {:ok, activity} <- insert(block_data, local),
340 :ok <- maybe_federate(activity) do
341 {:ok, activity}
342 else
343 _e -> {:ok, nil}
344 end
345 end
346
347 def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
348 with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
349 unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
350 {:ok, activity} <- insert(unblock_data, local),
351 :ok <- maybe_federate(activity) do
352 {:ok, activity}
353 end
354 end
355
356 def flag(
357 %{
358 actor: actor,
359 context: context,
360 account: account,
361 statuses: statuses,
362 content: content
363 } = params
364 ) do
365 additional = params[:additional] || %{}
366
367 # only accept false as false value
368 local = !(params[:local] == false)
369
370 %{
371 actor: actor,
372 context: context,
373 account: account,
374 statuses: statuses,
375 content: content
376 }
377 |> make_flag_data(additional)
378 |> insert(local)
379 end
380
381 def fetch_activities_for_context(context, opts \\ %{}) do
382 public = ["https://www.w3.org/ns/activitystreams#Public"]
383
384 recipients =
385 if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
386
387 query = from(activity in Activity)
388
389 query =
390 query
391 |> restrict_blocked(opts)
392 |> restrict_recipients(recipients, opts["user"])
393
394 query =
395 from(
396 activity in query,
397 where:
398 fragment(
399 "?->>'type' = ? and ?->>'context' = ?",
400 activity.data,
401 "Create",
402 activity.data,
403 ^context
404 ),
405 order_by: [desc: :id]
406 )
407
408 Repo.all(query)
409 end
410
411 def fetch_public_activities(opts \\ %{}) do
412 q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
413
414 q
415 |> restrict_unlisted()
416 |> Repo.all()
417 |> Enum.reverse()
418 end
419
420 @valid_visibilities ~w[direct unlisted public private]
421
422 defp restrict_visibility(query, %{visibility: visibility})
423 when visibility in @valid_visibilities do
424 query =
425 from(
426 a in query,
427 where:
428 fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
429 )
430
431 Ecto.Adapters.SQL.to_sql(:all, Repo, query)
432
433 query
434 end
435
436 defp restrict_visibility(_query, %{visibility: visibility})
437 when visibility not in @valid_visibilities do
438 Logger.error("Could not restrict visibility to #{visibility}")
439 end
440
441 defp restrict_visibility(query, _visibility), do: query
442
443 def fetch_user_activities(user, reading_user, params \\ %{}) do
444 params =
445 params
446 |> Map.put("type", ["Create", "Announce"])
447 |> Map.put("actor_id", user.ap_id)
448 |> Map.put("whole_db", true)
449 |> Map.put("pinned_activity_ids", user.info.pinned_activities)
450
451 recipients =
452 if reading_user do
453 ["https://www.w3.org/ns/activitystreams#Public"] ++
454 [reading_user.ap_id | reading_user.following]
455 else
456 ["https://www.w3.org/ns/activitystreams#Public"]
457 end
458
459 fetch_activities(recipients, params)
460 |> Enum.reverse()
461 end
462
463 defp restrict_since(query, %{"since_id" => ""}), do: query
464
465 defp restrict_since(query, %{"since_id" => since_id}) do
466 from(activity in query, where: activity.id > ^since_id)
467 end
468
469 defp restrict_since(query, _), do: query
470
471 defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})
472 when is_list(tag_reject) and tag_reject != [] do
473 from(
474 activity in query,
475 where: fragment("(not (? #> '{\"object\",\"tag\"}') \\?| ?)", activity.data, ^tag_reject)
476 )
477 end
478
479 defp restrict_tag_reject(query, _), do: query
480
481 defp restrict_tag_all(query, %{"tag_all" => tag_all})
482 when is_list(tag_all) and tag_all != [] do
483 from(
484 activity in query,
485 where: fragment("(? #> '{\"object\",\"tag\"}') \\?& ?", activity.data, ^tag_all)
486 )
487 end
488
489 defp restrict_tag_all(query, _), do: query
490
491 defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
492 from(
493 activity in query,
494 where: fragment("(? #> '{\"object\",\"tag\"}') \\?| ?", activity.data, ^tag)
495 )
496 end
497
498 defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
499 from(
500 activity in query,
501 where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
502 )
503 end
504
505 defp restrict_tag(query, _), do: query
506
507 defp restrict_to_cc(query, recipients_to, recipients_cc) do
508 from(
509 activity in query,
510 where:
511 fragment(
512 "(?->'to' \\?| ?) or (?->'cc' \\?| ?)",
513 activity.data,
514 ^recipients_to,
515 activity.data,
516 ^recipients_cc
517 )
518 )
519 end
520
521 defp restrict_recipients(query, [], _user), do: query
522
523 defp restrict_recipients(query, recipients, nil) do
524 from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
525 end
526
527 defp restrict_recipients(query, recipients, user) do
528 from(
529 activity in query,
530 where: fragment("? && ?", ^recipients, activity.recipients),
531 or_where: activity.actor == ^user.ap_id
532 )
533 end
534
535 defp restrict_limit(query, %{"limit" => limit}) do
536 from(activity in query, limit: ^limit)
537 end
538
539 defp restrict_limit(query, _), do: query
540
541 defp restrict_local(query, %{"local_only" => true}) do
542 from(activity in query, where: activity.local == true)
543 end
544
545 defp restrict_local(query, _), do: query
546
547 defp restrict_max(query, %{"max_id" => ""}), do: query
548
549 defp restrict_max(query, %{"max_id" => max_id}) do
550 from(activity in query, where: activity.id < ^max_id)
551 end
552
553 defp restrict_max(query, _), do: query
554
555 defp restrict_actor(query, %{"actor_id" => actor_id}) do
556 from(activity in query, where: activity.actor == ^actor_id)
557 end
558
559 defp restrict_actor(query, _), do: query
560
561 defp restrict_type(query, %{"type" => type}) when is_binary(type) do
562 from(activity in query, where: fragment("?->>'type' = ?", activity.data, ^type))
563 end
564
565 defp restrict_type(query, %{"type" => type}) do
566 from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
567 end
568
569 defp restrict_type(query, _), do: query
570
571 defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
572 from(
573 activity in query,
574 where: fragment("? <@ (? #> '{\"object\",\"likes\"}')", ^ap_id, activity.data)
575 )
576 end
577
578 defp restrict_favorited_by(query, _), do: query
579
580 defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
581 from(
582 activity in query,
583 where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
584 )
585 end
586
587 defp restrict_media(query, _), do: query
588
589 defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
590 from(
591 activity in query,
592 where: fragment("?->'object'->>'inReplyTo' is null", activity.data)
593 )
594 end
595
596 defp restrict_replies(query, _), do: query
597
598 defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do
599 from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
600 end
601
602 defp restrict_reblogs(query, _), do: query
603
604 defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
605 mutes = info.mutes
606
607 from(
608 activity in query,
609 where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
610 where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
611 )
612 end
613
614 defp restrict_muted(query, _), do: query
615
616 defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
617 blocks = info.blocks || []
618 domain_blocks = info.domain_blocks || []
619
620 from(
621 activity in query,
622 where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
623 where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks),
624 where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
625 )
626 end
627
628 defp restrict_blocked(query, _), do: query
629
630 defp restrict_unlisted(query) do
631 from(
632 activity in query,
633 where:
634 fragment(
635 "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
636 activity.data,
637 ^["https://www.w3.org/ns/activitystreams#Public"]
638 )
639 )
640 end
641
642 defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
643 from(activity in query, where: activity.id in ^ids)
644 end
645
646 defp restrict_pinned(query, _), do: query
647
648 def fetch_activities_query(recipients, opts \\ %{}) do
649 base_query =
650 from(
651 activity in Activity,
652 limit: 20,
653 order_by: [fragment("? desc nulls last", activity.id)]
654 )
655
656 base_query
657 |> restrict_recipients(recipients, opts["user"])
658 |> restrict_tag(opts)
659 |> restrict_tag_reject(opts)
660 |> restrict_tag_all(opts)
661 |> restrict_since(opts)
662 |> restrict_local(opts)
663 |> restrict_limit(opts)
664 |> restrict_max(opts)
665 |> restrict_actor(opts)
666 |> restrict_type(opts)
667 |> restrict_favorited_by(opts)
668 |> restrict_blocked(opts)
669 |> restrict_muted(opts)
670 |> restrict_media(opts)
671 |> restrict_visibility(opts)
672 |> restrict_replies(opts)
673 |> restrict_reblogs(opts)
674 |> restrict_pinned(opts)
675 end
676
677 def fetch_activities(recipients, opts \\ %{}) do
678 fetch_activities_query(recipients, opts)
679 |> Repo.all()
680 |> Enum.reverse()
681 end
682
683 def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do
684 fetch_activities_query([], opts)
685 |> restrict_to_cc(recipients_to, recipients_cc)
686 |> Repo.all()
687 |> Enum.reverse()
688 end
689
690 def upload(file, opts \\ []) do
691 with {:ok, data} <- Upload.store(file, opts) do
692 obj_data =
693 if opts[:actor] do
694 Map.put(data, "actor", opts[:actor])
695 else
696 data
697 end
698
699 Repo.insert(%Object{data: obj_data})
700 end
701 end
702
703 def user_data_from_user_object(data) do
704 avatar =
705 data["icon"]["url"] &&
706 %{
707 "type" => "Image",
708 "url" => [%{"href" => data["icon"]["url"]}]
709 }
710
711 banner =
712 data["image"]["url"] &&
713 %{
714 "type" => "Image",
715 "url" => [%{"href" => data["image"]["url"]}]
716 }
717
718 locked = data["manuallyApprovesFollowers"] || false
719 data = Transmogrifier.maybe_fix_user_object(data)
720
721 user_data = %{
722 ap_id: data["id"],
723 info: %{
724 "ap_enabled" => true,
725 "source_data" => data,
726 "banner" => banner,
727 "locked" => locked
728 },
729 avatar: avatar,
730 name: data["name"],
731 follower_address: data["followers"],
732 bio: data["summary"]
733 }
734
735 # nickname can be nil because of virtual actors
736 user_data =
737 if data["preferredUsername"] do
738 Map.put(
739 user_data,
740 :nickname,
741 "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
742 )
743 else
744 Map.put(user_data, :nickname, nil)
745 end
746
747 {:ok, user_data}
748 end
749
750 def fetch_and_prepare_user_from_ap_id(ap_id) do
751 with {:ok, data} <- fetch_and_contain_remote_object_from_id(ap_id) do
752 user_data_from_user_object(data)
753 else
754 e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
755 end
756 end
757
758 def make_user_from_ap_id(ap_id) do
759 if _user = User.get_by_ap_id(ap_id) do
760 Transmogrifier.upgrade_user_from_ap_id(ap_id)
761 else
762 with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
763 User.insert_or_update_user(data)
764 else
765 e -> {:error, e}
766 end
767 end
768 end
769
770 def make_user_from_nickname(nickname) do
771 with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
772 make_user_from_ap_id(ap_id)
773 else
774 _e -> {:error, "No AP id in WebFinger"}
775 end
776 end
777
778 def should_federate?(inbox, public) do
779 if public do
780 true
781 else
782 inbox_info = URI.parse(inbox)
783 !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
784 end
785 end
786
787 def publish(actor, activity) do
788 remote_followers =
789 if actor.follower_address in activity.recipients do
790 {:ok, followers} = User.get_followers(actor)
791 followers |> Enum.filter(&(!&1.local))
792 else
793 []
794 end
795
796 public = is_public?(activity)
797
798 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
799 json = Jason.encode!(data)
800
801 (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
802 |> Enum.filter(fn user -> User.ap_enabled?(user) end)
803 |> Enum.map(fn %{info: %{source_data: data}} ->
804 (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
805 end)
806 |> Enum.uniq()
807 |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
808 |> Instances.filter_reachable()
809 |> Enum.each(fn {inbox, unreachable_since} ->
810 Federator.publish_single_ap(%{
811 inbox: inbox,
812 json: json,
813 actor: actor,
814 id: activity.data["id"],
815 unreachable_since: unreachable_since
816 })
817 end)
818 end
819
820 def publish_one(%{inbox: inbox, json: json, actor: actor, id: id} = params) do
821 Logger.info("Federating #{id} to #{inbox}")
822 host = URI.parse(inbox).host
823
824 digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
825
826 signature =
827 Pleroma.Web.HTTPSignatures.sign(actor, %{
828 host: host,
829 "content-length": byte_size(json),
830 digest: digest
831 })
832
833 with {:ok, %{status: code}} when code in 200..299 <-
834 result =
835 @httpoison.post(
836 inbox,
837 json,
838 [
839 {"Content-Type", "application/activity+json"},
840 {"signature", signature},
841 {"digest", digest}
842 ]
843 ) do
844 if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
845 do: Instances.set_reachable(inbox)
846
847 result
848 else
849 {_post_result, response} ->
850 unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
851 {:error, response}
852 end
853 end
854
855 # TODO:
856 # This will create a Create activity, which we need internally at the moment.
857 def fetch_object_from_id(id) do
858 if object = Object.get_cached_by_ap_id(id) do
859 {:ok, object}
860 else
861 with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
862 nil <- Object.normalize(data),
863 params <- %{
864 "type" => "Create",
865 "to" => data["to"],
866 "cc" => data["cc"],
867 "actor" => data["actor"] || data["attributedTo"],
868 "object" => data
869 },
870 :ok <- Transmogrifier.contain_origin(id, params),
871 {:ok, activity} <- Transmogrifier.handle_incoming(params) do
872 {:ok, Object.normalize(activity.data["object"])}
873 else
874 {:error, {:reject, nil}} ->
875 {:reject, nil}
876
877 object = %Object{} ->
878 {:ok, object}
879
880 _e ->
881 Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
882
883 case OStatus.fetch_activity_from_url(id) do
884 {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
885 e -> e
886 end
887 end
888 end
889 end
890
891 def fetch_and_contain_remote_object_from_id(id) do
892 Logger.info("Fetching object #{id} via AP")
893
894 with true <- String.starts_with?(id, "http"),
895 {:ok, %{body: body, status: code}} when code in 200..299 <-
896 @httpoison.get(
897 id,
898 [{:Accept, "application/activity+json"}]
899 ),
900 {:ok, data} <- Jason.decode(body),
901 :ok <- Transmogrifier.contain_origin_from_id(id, data) do
902 {:ok, data}
903 else
904 e ->
905 {:error, e}
906 end
907 end
908
909 def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
910 def is_public?(%Object{data: data}), do: is_public?(data)
911 def is_public?(%Activity{data: data}), do: is_public?(data)
912 def is_public?(%{"directMessage" => true}), do: false
913
914 def is_public?(data) do
915 "https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
916 end
917
918 def is_private?(activity) do
919 unless is_public?(activity) do
920 follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address
921 Enum.any?(activity.data["to"], &(&1 == follower_address))
922 else
923 false
924 end
925 end
926
927 def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
928 def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
929
930 def is_direct?(activity) do
931 !is_public?(activity) && !is_private?(activity)
932 end
933
934 def visible_for_user?(activity, nil) do
935 is_public?(activity)
936 end
937
938 def visible_for_user?(activity, user) do
939 x = [user.ap_id | user.following]
940 y = activity.data["to"] ++ (activity.data["cc"] || [])
941 visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
942 end
943
944 # guard
945 def entire_thread_visible_for_user?(nil, _user), do: false
946
947 # child
948 def entire_thread_visible_for_user?(
949 %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
950 user
951 )
952 when is_binary(parent_id) do
953 parent = Activity.get_in_reply_to_activity(tail)
954 visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
955 end
956
957 # root
958 def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user)
959
960 # filter out broken threads
961 def contain_broken_threads(%Activity{} = activity, %User{} = user) do
962 entire_thread_visible_for_user?(activity, user)
963 end
964
965 # do post-processing on a specific activity
966 def contain_activity(%Activity{} = activity, %User{} = user) do
967 contain_broken_threads(activity, user)
968 end
969
970 # do post-processing on a timeline
971 def contain_timeline(timeline, user) do
972 timeline
973 |> Enum.filter(fn activity ->
974 contain_activity(activity, user)
975 end)
976 end
977 end