Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop
[akkoma] / lib / pleroma / web / activity_pub / activity_pub_controller.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.ActivityPubController do
6 use Pleroma.Web, :controller
7
8 alias Pleroma.Activity
9 alias Pleroma.Object
10 alias Pleroma.Object.Fetcher
11 alias Pleroma.User
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.ObjectView
14 alias Pleroma.Web.ActivityPub.Relay
15 alias Pleroma.Web.ActivityPub.Transmogrifier
16 alias Pleroma.Web.ActivityPub.UserView
17 alias Pleroma.Web.ActivityPub.Utils
18 alias Pleroma.Web.ActivityPub.Visibility
19 alias Pleroma.Web.Federator
20
21 require Logger
22
23 action_fallback(:errors)
24
25 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
26 plug(:set_requester_reachable when action in [:inbox])
27 plug(:relay_active? when action in [:relay])
28
29 def relay_active?(conn, _) do
30 if Pleroma.Config.get([:instance, :allow_relay]) do
31 conn
32 else
33 conn
34 |> render_error(:not_found, "not found")
35 |> halt()
36 end
37 end
38
39 def user(conn, %{"nickname" => nickname}) do
40 with %User{} = user <- User.get_cached_by_nickname(nickname),
41 {:ok, user} <- User.ensure_keys_present(user) do
42 conn
43 |> put_resp_header("content-type", "application/activity+json")
44 |> json(UserView.render("user.json", %{user: user}))
45 else
46 nil -> {:error, :not_found}
47 end
48 end
49
50 def object(conn, %{"uuid" => uuid}) do
51 with ap_id <- o_status_url(conn, :object, uuid),
52 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
53 {_, true} <- {:public?, Visibility.is_public?(object)} do
54 conn
55 |> put_resp_header("content-type", "application/activity+json")
56 |> json(ObjectView.render("object.json", %{object: object}))
57 else
58 {:public?, false} ->
59 {:error, :not_found}
60 end
61 end
62
63 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
64 with ap_id <- o_status_url(conn, :object, uuid),
65 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
66 {_, true} <- {:public?, Visibility.is_public?(object)},
67 likes <- Utils.get_object_likes(object) do
68 {page, _} = Integer.parse(page)
69
70 conn
71 |> put_resp_header("content-type", "application/activity+json")
72 |> json(ObjectView.render("likes.json", ap_id, likes, page))
73 else
74 {:public?, false} ->
75 {:error, :not_found}
76 end
77 end
78
79 def object_likes(conn, %{"uuid" => uuid}) do
80 with ap_id <- o_status_url(conn, :object, uuid),
81 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
82 {_, true} <- {:public?, Visibility.is_public?(object)},
83 likes <- Utils.get_object_likes(object) do
84 conn
85 |> put_resp_header("content-type", "application/activity+json")
86 |> json(ObjectView.render("likes.json", ap_id, likes))
87 else
88 {:public?, false} ->
89 {:error, :not_found}
90 end
91 end
92
93 def activity(conn, %{"uuid" => uuid}) do
94 with ap_id <- o_status_url(conn, :activity, uuid),
95 %Activity{} = activity <- Activity.normalize(ap_id),
96 {_, true} <- {:public?, Visibility.is_public?(activity)} do
97 conn
98 |> put_resp_header("content-type", "application/activity+json")
99 |> json(ObjectView.render("object.json", %{object: activity}))
100 else
101 {:public?, false} ->
102 {:error, :not_found}
103 end
104 end
105
106 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
107 with %User{} = user <- User.get_cached_by_nickname(nickname),
108 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
109 {:show_follows, true} <-
110 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
111 {page, _} = Integer.parse(page)
112
113 conn
114 |> put_resp_header("content-type", "application/activity+json")
115 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
116 else
117 {:show_follows, _} ->
118 conn
119 |> put_resp_header("content-type", "application/activity+json")
120 |> send_resp(403, "")
121 end
122 end
123
124 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
125 with %User{} = user <- User.get_cached_by_nickname(nickname),
126 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
127 conn
128 |> put_resp_header("content-type", "application/activity+json")
129 |> json(UserView.render("following.json", %{user: user, for: for_user}))
130 end
131 end
132
133 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
134 with %User{} = user <- User.get_cached_by_nickname(nickname),
135 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
136 {:show_followers, true} <-
137 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
138 {page, _} = Integer.parse(page)
139
140 conn
141 |> put_resp_header("content-type", "application/activity+json")
142 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
143 else
144 {:show_followers, _} ->
145 conn
146 |> put_resp_header("content-type", "application/activity+json")
147 |> send_resp(403, "")
148 end
149 end
150
151 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
152 with %User{} = user <- User.get_cached_by_nickname(nickname),
153 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
154 conn
155 |> put_resp_header("content-type", "application/activity+json")
156 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
157 end
158 end
159
160 def outbox(conn, %{"nickname" => nickname} = params) do
161 with %User{} = user <- User.get_cached_by_nickname(nickname),
162 {:ok, user} <- User.ensure_keys_present(user) do
163 conn
164 |> put_resp_header("content-type", "application/activity+json")
165 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
166 end
167 end
168
169 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
170 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
171 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
172 true <- Utils.recipient_in_message(recipient, actor, params),
173 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
174 Federator.incoming_ap_doc(params)
175 json(conn, "ok")
176 end
177 end
178
179 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
180 Federator.incoming_ap_doc(params)
181 json(conn, "ok")
182 end
183
184 # only accept relayed Creates
185 def inbox(conn, %{"type" => "Create"} = params) do
186 Logger.info(
187 "Signature missing or not from author, relayed Create message, fetching object from source"
188 )
189
190 Fetcher.fetch_object_from_id(params["object"]["id"])
191
192 json(conn, "ok")
193 end
194
195 def inbox(conn, params) do
196 headers = Enum.into(conn.req_headers, %{})
197
198 if String.contains?(headers["signature"], params["actor"]) do
199 Logger.info(
200 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
201 )
202
203 Logger.info(inspect(conn.req_headers))
204 end
205
206 json(conn, dgettext("errors", "error"))
207 end
208
209 def relay(conn, _params) do
210 with %User{} = user <- Relay.get_actor(),
211 {:ok, user} <- User.ensure_keys_present(user) do
212 conn
213 |> put_resp_header("content-type", "application/activity+json")
214 |> json(UserView.render("user.json", %{user: user}))
215 else
216 nil -> {:error, :not_found}
217 end
218 end
219
220 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
221 conn
222 |> put_resp_header("content-type", "application/activity+json")
223 |> json(UserView.render("user.json", %{user: user}))
224 end
225
226 def whoami(_conn, _params), do: {:error, :not_found}
227
228 def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
229 if nickname == user.nickname do
230 conn
231 |> put_resp_header("content-type", "application/activity+json")
232 |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
233 else
234 err =
235 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
236 nickname: nickname,
237 as_nickname: user.nickname
238 )
239
240 conn
241 |> put_status(:forbidden)
242 |> json(err)
243 end
244 end
245
246 def handle_user_activity(user, %{"type" => "Create"} = params) do
247 object =
248 params["object"]
249 |> Map.merge(Map.take(params, ["to", "cc"]))
250 |> Map.put("attributedTo", user.ap_id())
251 |> Transmogrifier.fix_object()
252
253 ActivityPub.create(%{
254 to: params["to"],
255 actor: user,
256 context: object["context"],
257 object: object,
258 additional: Map.take(params, ["cc"])
259 })
260 end
261
262 def handle_user_activity(user, %{"type" => "Delete"} = params) do
263 with %Object{} = object <- Object.normalize(params["object"]),
264 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
265 {:ok, delete} <- ActivityPub.delete(object) do
266 {:ok, delete}
267 else
268 _ -> {:error, dgettext("errors", "Can't delete object")}
269 end
270 end
271
272 def handle_user_activity(user, %{"type" => "Like"} = params) do
273 with %Object{} = object <- Object.normalize(params["object"]),
274 {:ok, activity, _object} <- ActivityPub.like(user, object) do
275 {:ok, activity}
276 else
277 _ -> {:error, dgettext("errors", "Can't like object")}
278 end
279 end
280
281 def handle_user_activity(_, _) do
282 {:error, dgettext("errors", "Unhandled activity type")}
283 end
284
285 def update_outbox(
286 %{assigns: %{user: user}} = conn,
287 %{"nickname" => nickname} = params
288 ) do
289 if nickname == user.nickname do
290 actor = user.ap_id()
291
292 params =
293 params
294 |> Map.drop(["id"])
295 |> Map.put("actor", actor)
296 |> Transmogrifier.fix_addressing()
297
298 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
299 conn
300 |> put_status(:created)
301 |> put_resp_header("location", activity.data["id"])
302 |> json(activity.data)
303 else
304 {:error, message} ->
305 conn
306 |> put_status(:bad_request)
307 |> json(message)
308 end
309 else
310 err =
311 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
312 nickname: nickname,
313 as_nickname: user.nickname
314 )
315
316 conn
317 |> put_status(:forbidden)
318 |> json(err)
319 end
320 end
321
322 def errors(conn, {:error, :not_found}) do
323 conn
324 |> put_status(:not_found)
325 |> json(dgettext("errors", "Not found"))
326 end
327
328 def errors(conn, _e) do
329 conn
330 |> put_status(:internal_server_error)
331 |> json(dgettext("errors", "error"))
332 end
333
334 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
335 with actor <- conn.params["actor"],
336 true <- is_binary(actor) do
337 Pleroma.Instances.set_reachable(actor)
338 end
339
340 conn
341 end
342
343 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
344 {:ok, new_user} = User.ensure_keys_present(user)
345
346 for_user =
347 if new_user != user and match?(%User{}, for_user) do
348 User.get_cached_by_nickname(for_user.nickname)
349 else
350 for_user
351 end
352
353 {new_user, for_user}
354 end
355 end