Merge branch 'bugfix/236-ambiguous-route-for-numerical-handles' 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, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
606
607 defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
608 mutes = info.mutes
609
610 from(
611 activity in query,
612 where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
613 where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
614 )
615 end
616
617 defp restrict_muted(query, _), do: query
618
619 defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
620 blocks = info.blocks || []
621 domain_blocks = info.domain_blocks || []
622
623 from(
624 activity in query,
625 where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
626 where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks),
627 where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
628 )
629 end
630
631 defp restrict_blocked(query, _), do: query
632
633 defp restrict_unlisted(query) do
634 from(
635 activity in query,
636 where:
637 fragment(
638 "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
639 activity.data,
640 ^["https://www.w3.org/ns/activitystreams#Public"]
641 )
642 )
643 end
644
645 defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
646 from(activity in query, where: activity.id in ^ids)
647 end
648
649 defp restrict_pinned(query, _), do: query
650
651 def fetch_activities_query(recipients, opts \\ %{}) do
652 base_query =
653 from(
654 activity in Activity,
655 limit: 20,
656 order_by: [fragment("? desc nulls last", activity.id)]
657 )
658
659 base_query
660 |> restrict_recipients(recipients, opts["user"])
661 |> restrict_tag(opts)
662 |> restrict_tag_reject(opts)
663 |> restrict_tag_all(opts)
664 |> restrict_since(opts)
665 |> restrict_local(opts)
666 |> restrict_limit(opts)
667 |> restrict_max(opts)
668 |> restrict_actor(opts)
669 |> restrict_type(opts)
670 |> restrict_favorited_by(opts)
671 |> restrict_blocked(opts)
672 |> restrict_muted(opts)
673 |> restrict_media(opts)
674 |> restrict_visibility(opts)
675 |> restrict_replies(opts)
676 |> restrict_reblogs(opts)
677 |> restrict_pinned(opts)
678 end
679
680 def fetch_activities(recipients, opts \\ %{}) do
681 fetch_activities_query(recipients, opts)
682 |> Repo.all()
683 |> Enum.reverse()
684 end
685
686 def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do
687 fetch_activities_query([], opts)
688 |> restrict_to_cc(recipients_to, recipients_cc)
689 |> Repo.all()
690 |> Enum.reverse()
691 end
692
693 def upload(file, opts \\ []) do
694 with {:ok, data} <- Upload.store(file, opts) do
695 obj_data =
696 if opts[:actor] do
697 Map.put(data, "actor", opts[:actor])
698 else
699 data
700 end
701
702 Repo.insert(%Object{data: obj_data})
703 end
704 end
705
706 def user_data_from_user_object(data) do
707 avatar =
708 data["icon"]["url"] &&
709 %{
710 "type" => "Image",
711 "url" => [%{"href" => data["icon"]["url"]}]
712 }
713
714 banner =
715 data["image"]["url"] &&
716 %{
717 "type" => "Image",
718 "url" => [%{"href" => data["image"]["url"]}]
719 }
720
721 locked = data["manuallyApprovesFollowers"] || false
722 data = Transmogrifier.maybe_fix_user_object(data)
723
724 user_data = %{
725 ap_id: data["id"],
726 info: %{
727 "ap_enabled" => true,
728 "source_data" => data,
729 "banner" => banner,
730 "locked" => locked
731 },
732 avatar: avatar,
733 name: data["name"],
734 follower_address: data["followers"],
735 bio: data["summary"]
736 }
737
738 # nickname can be nil because of virtual actors
739 user_data =
740 if data["preferredUsername"] do
741 Map.put(
742 user_data,
743 :nickname,
744 "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
745 )
746 else
747 Map.put(user_data, :nickname, nil)
748 end
749
750 {:ok, user_data}
751 end
752
753 def fetch_and_prepare_user_from_ap_id(ap_id) do
754 with {:ok, data} <- fetch_and_contain_remote_object_from_id(ap_id) do
755 user_data_from_user_object(data)
756 else
757 e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
758 end
759 end
760
761 def make_user_from_ap_id(ap_id) do
762 if _user = User.get_by_ap_id(ap_id) do
763 Transmogrifier.upgrade_user_from_ap_id(ap_id)
764 else
765 with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
766 User.insert_or_update_user(data)
767 else
768 e -> {:error, e}
769 end
770 end
771 end
772
773 def make_user_from_nickname(nickname) do
774 with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
775 make_user_from_ap_id(ap_id)
776 else
777 _e -> {:error, "No AP id in WebFinger"}
778 end
779 end
780
781 def should_federate?(inbox, public) do
782 if public do
783 true
784 else
785 inbox_info = URI.parse(inbox)
786 !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
787 end
788 end
789
790 def publish(actor, activity) do
791 remote_followers =
792 if actor.follower_address in activity.recipients do
793 {:ok, followers} = User.get_followers(actor)
794 followers |> Enum.filter(&(!&1.local))
795 else
796 []
797 end
798
799 public = is_public?(activity)
800
801 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
802 json = Jason.encode!(data)
803
804 (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
805 |> Enum.filter(fn user -> User.ap_enabled?(user) end)
806 |> Enum.map(fn %{info: %{source_data: data}} ->
807 (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
808 end)
809 |> Enum.uniq()
810 |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
811 |> Instances.filter_reachable()
812 |> Enum.each(fn {inbox, unreachable_since} ->
813 Federator.publish_single_ap(%{
814 inbox: inbox,
815 json: json,
816 actor: actor,
817 id: activity.data["id"],
818 unreachable_since: unreachable_since
819 })
820 end)
821 end
822
823 def publish_one(%{inbox: inbox, json: json, actor: actor, id: id} = params) do
824 Logger.info("Federating #{id} to #{inbox}")
825 host = URI.parse(inbox).host
826
827 digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
828
829 date =
830 NaiveDateTime.utc_now()
831 |> Timex.format!("{WDshort}, {D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
832
833 signature =
834 Pleroma.Web.HTTPSignatures.sign(actor, %{
835 host: host,
836 "content-length": byte_size(json),
837 digest: digest,
838 date: date
839 })
840
841 with {:ok, %{status: code}} when code in 200..299 <-
842 result =
843 @httpoison.post(
844 inbox,
845 json,
846 [
847 {"Content-Type", "application/activity+json"},
848 {"Date", date},
849 {"signature", signature},
850 {"digest", digest}
851 ]
852 ) do
853 if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
854 do: Instances.set_reachable(inbox)
855
856 result
857 else
858 {_post_result, response} ->
859 unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
860 {:error, response}
861 end
862 end
863
864 # TODO:
865 # This will create a Create activity, which we need internally at the moment.
866 def fetch_object_from_id(id) do
867 if object = Object.get_cached_by_ap_id(id) do
868 {:ok, object}
869 else
870 with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
871 nil <- Object.normalize(data),
872 params <- %{
873 "type" => "Create",
874 "to" => data["to"],
875 "cc" => data["cc"],
876 "actor" => data["actor"] || data["attributedTo"],
877 "object" => data
878 },
879 :ok <- Transmogrifier.contain_origin(id, params),
880 {:ok, activity} <- Transmogrifier.handle_incoming(params) do
881 {:ok, Object.normalize(activity.data["object"])}
882 else
883 {:error, {:reject, nil}} ->
884 {:reject, nil}
885
886 object = %Object{} ->
887 {:ok, object}
888
889 _e ->
890 Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
891
892 case OStatus.fetch_activity_from_url(id) do
893 {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
894 e -> e
895 end
896 end
897 end
898 end
899
900 def fetch_and_contain_remote_object_from_id(id) do
901 Logger.info("Fetching object #{id} via AP")
902
903 with true <- String.starts_with?(id, "http"),
904 {:ok, %{body: body, status: code}} when code in 200..299 <-
905 @httpoison.get(
906 id,
907 [{:Accept, "application/activity+json"}]
908 ),
909 {:ok, data} <- Jason.decode(body),
910 :ok <- Transmogrifier.contain_origin_from_id(id, data) do
911 {:ok, data}
912 else
913 e ->
914 {:error, e}
915 end
916 end
917
918 # filter out broken threads
919 def contain_broken_threads(%Activity{} = activity, %User{} = user) do
920 entire_thread_visible_for_user?(activity, user)
921 end
922
923 # do post-processing on a specific activity
924 def contain_activity(%Activity{} = activity, %User{} = user) do
925 contain_broken_threads(activity, user)
926 end
927
928 # do post-processing on a timeline
929 def contain_timeline(timeline, user) do
930 timeline
931 |> Enum.filter(fn activity ->
932 contain_activity(activity, user)
933 end)
934 end
935 end