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