Merge branch 'develop' into tests/mastodon_api_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 |> put_view(UserView)
53 |> render("user.json", %{user: user})
54 else
55 nil -> {:error, :not_found}
56 end
57 end
58
59 def object(conn, %{"uuid" => uuid}) do
60 with ap_id <- o_status_url(conn, :object, uuid),
61 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
62 {_, true} <- {:public?, Visibility.is_public?(object)} do
63 conn
64 |> assign(:tracking_fun_data, object.id)
65 |> set_cache_ttl_for(object)
66 |> put_resp_content_type("application/activity+json")
67 |> put_view(ObjectView)
68 |> render("object.json", object: object)
69 else
70 {:public?, false} ->
71 {:error, :not_found}
72 end
73 end
74
75 def track_object_fetch(conn, nil), do: conn
76
77 def track_object_fetch(conn, object_id) do
78 with %{assigns: %{user: %User{id: user_id}}} <- conn do
79 Delivery.create(object_id, user_id)
80 end
81
82 conn
83 end
84
85 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
86 with ap_id <- o_status_url(conn, :object, uuid),
87 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
88 {_, true} <- {:public?, Visibility.is_public?(object)},
89 likes <- Utils.get_object_likes(object) do
90 {page, _} = Integer.parse(page)
91
92 conn
93 |> put_resp_content_type("application/activity+json")
94 |> put_view(ObjectView)
95 |> render("likes.json", %{ap_id: ap_id, likes: likes, page: page})
96 else
97 {:public?, false} ->
98 {:error, :not_found}
99 end
100 end
101
102 def object_likes(conn, %{"uuid" => uuid}) do
103 with ap_id <- o_status_url(conn, :object, uuid),
104 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
105 {_, true} <- {:public?, Visibility.is_public?(object)},
106 likes <- Utils.get_object_likes(object) do
107 conn
108 |> put_resp_content_type("application/activity+json")
109 |> put_view(ObjectView)
110 |> render("likes.json", %{ap_id: ap_id, likes: likes})
111 else
112 {:public?, false} ->
113 {:error, :not_found}
114 end
115 end
116
117 def activity(conn, %{"uuid" => uuid}) do
118 with ap_id <- o_status_url(conn, :activity, uuid),
119 %Activity{} = activity <- Activity.normalize(ap_id),
120 {_, true} <- {:public?, Visibility.is_public?(activity)} do
121 conn
122 |> maybe_set_tracking_data(activity)
123 |> set_cache_ttl_for(activity)
124 |> put_resp_content_type("application/activity+json")
125 |> put_view(ObjectView)
126 |> render("object.json", object: activity)
127 else
128 {:public?, false} -> {:error, :not_found}
129 nil -> {:error, :not_found}
130 end
131 end
132
133 defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
134 object_id = Object.normalize(activity).id
135 assign(conn, :tracking_fun_data, object_id)
136 end
137
138 defp maybe_set_tracking_data(conn, _activity), do: conn
139
140 defp set_cache_ttl_for(conn, %Activity{object: object}) do
141 set_cache_ttl_for(conn, object)
142 end
143
144 defp set_cache_ttl_for(conn, entity) do
145 ttl =
146 case entity do
147 %Object{data: %{"type" => "Question"}} ->
148 Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
149
150 %Object{} ->
151 Pleroma.Config.get([:web_cache_ttl, :activity_pub])
152
153 _ ->
154 nil
155 end
156
157 assign(conn, :cache_ttl, ttl)
158 end
159
160 # GET /relay/following
161 def following(%{assigns: %{relay: true}} = conn, _params) do
162 conn
163 |> put_resp_content_type("application/activity+json")
164 |> put_view(UserView)
165 |> render("following.json", %{user: Relay.get_actor()})
166 end
167
168 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
169 with %User{} = user <- User.get_cached_by_nickname(nickname),
170 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
171 {:show_follows, true} <-
172 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
173 {page, _} = Integer.parse(page)
174
175 conn
176 |> put_resp_content_type("application/activity+json")
177 |> put_view(UserView)
178 |> render("following.json", %{user: user, page: page, for: for_user})
179 else
180 {:show_follows, _} ->
181 conn
182 |> put_resp_content_type("application/activity+json")
183 |> send_resp(403, "")
184 end
185 end
186
187 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
188 with %User{} = user <- User.get_cached_by_nickname(nickname),
189 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
190 conn
191 |> put_resp_content_type("application/activity+json")
192 |> put_view(UserView)
193 |> render("following.json", %{user: user, for: for_user})
194 end
195 end
196
197 # GET /relay/followers
198 def followers(%{assigns: %{relay: true}} = conn, _params) do
199 conn
200 |> put_resp_content_type("application/activity+json")
201 |> put_view(UserView)
202 |> render("followers.json", %{user: Relay.get_actor()})
203 end
204
205 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
206 with %User{} = user <- User.get_cached_by_nickname(nickname),
207 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
208 {:show_followers, true} <-
209 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
210 {page, _} = Integer.parse(page)
211
212 conn
213 |> put_resp_content_type("application/activity+json")
214 |> put_view(UserView)
215 |> render("followers.json", %{user: user, page: page, for: for_user})
216 else
217 {:show_followers, _} ->
218 conn
219 |> put_resp_content_type("application/activity+json")
220 |> send_resp(403, "")
221 end
222 end
223
224 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
225 with %User{} = user <- User.get_cached_by_nickname(nickname),
226 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
227 conn
228 |> put_resp_content_type("application/activity+json")
229 |> put_view(UserView)
230 |> render("followers.json", %{user: user, for: for_user})
231 end
232 end
233
234 def outbox(conn, %{"nickname" => nickname, "page" => page?} = params)
235 when page? in [true, "true"] do
236 with %User{} = user <- User.get_cached_by_nickname(nickname),
237 {:ok, user} <- User.ensure_keys_present(user) do
238 activities =
239 if params["max_id"] do
240 ActivityPub.fetch_user_activities(user, nil, %{
241 "max_id" => params["max_id"],
242 # This is a hack because postgres generates inefficient queries when filtering by
243 # 'Answer', poll votes will be hidden by the visibility filter in this case anyway
244 "include_poll_votes" => true,
245 "limit" => 10
246 })
247 else
248 ActivityPub.fetch_user_activities(user, nil, %{
249 "limit" => 10,
250 "include_poll_votes" => true
251 })
252 end
253
254 conn
255 |> put_resp_content_type("application/activity+json")
256 |> put_view(UserView)
257 |> render("activity_collection_page.json", %{
258 activities: activities,
259 iri: "#{user.ap_id}/outbox"
260 })
261 end
262 end
263
264 def outbox(conn, %{"nickname" => nickname}) do
265 with %User{} = user <- User.get_cached_by_nickname(nickname),
266 {:ok, user} <- User.ensure_keys_present(user) do
267 conn
268 |> put_resp_content_type("application/activity+json")
269 |> put_view(UserView)
270 |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
271 end
272 end
273
274 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
275 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
276 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
277 true <- Utils.recipient_in_message(recipient, actor, params),
278 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
279 Federator.incoming_ap_doc(params)
280 json(conn, "ok")
281 end
282 end
283
284 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
285 Federator.incoming_ap_doc(params)
286 json(conn, "ok")
287 end
288
289 # only accept relayed Creates
290 def inbox(conn, %{"type" => "Create"} = params) do
291 Logger.info(
292 "Signature missing or not from author, relayed Create message, fetching object from source"
293 )
294
295 Fetcher.fetch_object_from_id(params["object"]["id"])
296
297 json(conn, "ok")
298 end
299
300 def inbox(conn, params) do
301 headers = Enum.into(conn.req_headers, %{})
302
303 if String.contains?(headers["signature"], params["actor"]) do
304 Logger.info(
305 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
306 )
307
308 Logger.info(inspect(conn.req_headers))
309 end
310
311 json(conn, dgettext("errors", "error"))
312 end
313
314 defp represent_service_actor(%User{} = user, conn) do
315 with {:ok, user} <- User.ensure_keys_present(user) do
316 conn
317 |> put_resp_content_type("application/activity+json")
318 |> put_view(UserView)
319 |> render("user.json", %{user: user})
320 else
321 nil -> {:error, :not_found}
322 end
323 end
324
325 defp represent_service_actor(nil, _), do: {:error, :not_found}
326
327 def relay(conn, _params) do
328 Relay.get_actor()
329 |> represent_service_actor(conn)
330 end
331
332 def internal_fetch(conn, _params) do
333 InternalFetchActor.get_actor()
334 |> represent_service_actor(conn)
335 end
336
337 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
338 conn
339 |> put_resp_content_type("application/activity+json")
340 |> put_view(UserView)
341 |> render("user.json", %{user: user})
342 end
343
344 def whoami(_conn, _params), do: {:error, :not_found}
345
346 def read_inbox(
347 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
348 %{"nickname" => nickname, "page" => page?} = params
349 )
350 when page? in [true, "true"] do
351 activities =
352 if params["max_id"] do
353 ActivityPub.fetch_activities([user.ap_id | user.following], %{
354 "max_id" => params["max_id"],
355 "limit" => 10
356 })
357 else
358 ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
359 end
360
361 conn
362 |> put_resp_content_type("application/activity+json")
363 |> put_view(UserView)
364 |> render("activity_collection_page.json", %{
365 activities: activities,
366 iri: "#{user.ap_id}/inbox"
367 })
368 end
369
370 def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
371 "nickname" => nickname
372 }) do
373 with {:ok, user} <- User.ensure_keys_present(user) do
374 conn
375 |> put_resp_content_type("application/activity+json")
376 |> put_view(UserView)
377 |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
378 end
379 end
380
381 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
382 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
383
384 conn
385 |> put_status(:forbidden)
386 |> json(err)
387 end
388
389 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
390 "nickname" => nickname
391 }) do
392 err =
393 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
394 nickname: nickname,
395 as_nickname: as_nickname
396 )
397
398 conn
399 |> put_status(:forbidden)
400 |> json(err)
401 end
402
403 def handle_user_activity(user, %{"type" => "Create"} = params) do
404 object =
405 params["object"]
406 |> Map.merge(Map.take(params, ["to", "cc"]))
407 |> Map.put("attributedTo", user.ap_id())
408 |> Transmogrifier.fix_object()
409
410 ActivityPub.create(%{
411 to: params["to"],
412 actor: user,
413 context: object["context"],
414 object: object,
415 additional: Map.take(params, ["cc"])
416 })
417 end
418
419 def handle_user_activity(user, %{"type" => "Delete"} = params) do
420 with %Object{} = object <- Object.normalize(params["object"]),
421 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
422 {:ok, delete} <- ActivityPub.delete(object) do
423 {:ok, delete}
424 else
425 _ -> {:error, dgettext("errors", "Can't delete object")}
426 end
427 end
428
429 def handle_user_activity(user, %{"type" => "Like"} = params) do
430 with %Object{} = object <- Object.normalize(params["object"]),
431 {:ok, activity, _object} <- ActivityPub.like(user, object) do
432 {:ok, activity}
433 else
434 _ -> {:error, dgettext("errors", "Can't like object")}
435 end
436 end
437
438 def handle_user_activity(_, _) do
439 {:error, dgettext("errors", "Unhandled activity type")}
440 end
441
442 def update_outbox(
443 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
444 %{"nickname" => nickname} = params
445 ) do
446 actor = user.ap_id()
447
448 params =
449 params
450 |> Map.drop(["id"])
451 |> Map.put("actor", actor)
452 |> Transmogrifier.fix_addressing()
453
454 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
455 conn
456 |> put_status(:created)
457 |> put_resp_header("location", activity.data["id"])
458 |> json(activity.data)
459 else
460 {:error, message} ->
461 conn
462 |> put_status(:bad_request)
463 |> json(message)
464 end
465 end
466
467 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
468 err =
469 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
470 nickname: nickname,
471 as_nickname: user.nickname
472 )
473
474 conn
475 |> put_status(:forbidden)
476 |> json(err)
477 end
478
479 def errors(conn, {:error, :not_found}) do
480 conn
481 |> put_status(:not_found)
482 |> json(dgettext("errors", "Not found"))
483 end
484
485 def errors(conn, _e) do
486 conn
487 |> put_status(:internal_server_error)
488 |> json(dgettext("errors", "error"))
489 end
490
491 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
492 with actor <- conn.params["actor"],
493 true <- is_binary(actor) do
494 Pleroma.Instances.set_reachable(actor)
495 end
496
497 conn
498 end
499
500 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
501 {:ok, new_user} = User.ensure_keys_present(user)
502
503 for_user =
504 if new_user != user and match?(%User{}, for_user) do
505 User.get_cached_by_nickname(for_user.nickname)
506 else
507 for_user
508 end
509
510 {new_user, for_user}
511 end
512 end