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