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