ee9f0fdd304b4d7b6a7f7c7dd9bcfb1bfc882ba6
[akkoma] / lib / pleroma / web / activity_pub / publisher.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.Publisher do
6 alias Pleroma.Activity
7 alias Pleroma.Instances
8 alias Pleroma.User
9 alias Pleroma.Web.ActivityPub.Transmogrifier
10
11 import Pleroma.Web.ActivityPub.Visibility
12
13 @behaviour Pleroma.Web.Federator.Publisher
14
15 require Logger
16
17 @httpoison Application.get_env(:pleroma, :httpoison)
18
19 @moduledoc """
20 ActivityPub outgoing federation module.
21 """
22
23 @doc """
24 Determine if an activity can be represented by running it through Transmogrifier.
25 """
26 def is_representable?(%Activity{} = activity) do
27 with %{} = _data <- Transmogrifier.prepare_outgoing(activity.data) do
28 true
29 else
30 _e -> false
31 end
32 end
33
34 @doc """
35 Publish a single message to a peer. Takes a struct with the following
36 parameters set:
37
38 * `inbox`: the inbox to publish to
39 * `json`: the JSON message body representing the ActivityPub message
40 * `actor`: the actor which is signing the message
41 * `id`: the ActivityStreams URI of the message
42 """
43 def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
44 Logger.info("Federating #{id} to #{inbox}")
45 host = URI.parse(inbox).host
46
47 digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
48
49 date =
50 NaiveDateTime.utc_now()
51 |> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
52
53 signature =
54 Pleroma.Web.HTTPSignatures.sign(actor, %{
55 host: host,
56 "content-length": byte_size(json),
57 digest: digest,
58 date: date
59 })
60
61 with {:ok, %{status: code}} when code in 200..299 <-
62 result =
63 @httpoison.post(
64 inbox,
65 json,
66 [
67 {"Content-Type", "application/activity+json"},
68 {"Date", date},
69 {"signature", signature},
70 {"digest", digest}
71 ]
72 ) do
73 if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
74 do: Instances.set_reachable(inbox)
75
76 result
77 else
78 {_post_result, response} ->
79 unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
80 {:error, response}
81 end
82 end
83
84 defp should_federate?(inbox, public) do
85 if public do
86 true
87 else
88 inbox_info = URI.parse(inbox)
89 !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
90 end
91 end
92
93 @doc """
94 Publishes an activity to all relevant peers.
95 """
96 def publish(%User{} = actor, %Activity{} = activity) do
97 remote_followers =
98 if actor.follower_address in activity.recipients do
99 {:ok, followers} = User.get_followers(actor)
100 followers |> Enum.filter(&(!&1.local))
101 else
102 []
103 end
104
105 public = is_public?(activity)
106
107 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
108 json = Jason.encode!(data)
109
110 (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
111 |> Enum.filter(fn user -> User.ap_enabled?(user) end)
112 |> Enum.map(fn %{info: %{source_data: data}} ->
113 (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
114 end)
115 |> Enum.uniq()
116 |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
117 |> Instances.filter_reachable()
118 |> Enum.each(fn {inbox, unreachable_since} ->
119 Pleroma.Web.Federator.Publisher.enqueue_one(
120 __MODULE__,
121 %{
122 inbox: inbox,
123 json: json,
124 actor: actor,
125 id: activity.data["id"],
126 unreachable_since: unreachable_since
127 }
128 )
129 end)
130 end
131 end