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