8069d64c9f4f892c7426d1f08c93158cfe8c7407
[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),
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 'Answer',
209 # 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) do
219 conn
220 |> put_resp_content_type("application/activity+json")
221 |> put_view(UserView)
222 |> render("activity_collection_page.json", %{
223 activities: activities,
224 iri: "#{user.ap_id}/outbox"
225 })
226 end
227 end
228
229 def outbox(conn, %{"nickname" => nickname}) do
230 with %User{} = user <- User.get_cached_by_nickname(nickname),
231 {:ok, user} <- User.ensure_keys_present(user) do
232 conn
233 |> put_resp_content_type("application/activity+json")
234 |> put_view(UserView)
235 |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
236 end
237 end
238
239 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
240 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
241 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
242 true <- Utils.recipient_in_message(recipient, actor, params),
243 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
244 Federator.incoming_ap_doc(params)
245 json(conn, "ok")
246 end
247 end
248
249 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
250 Federator.incoming_ap_doc(params)
251 json(conn, "ok")
252 end
253
254 # only accept relayed Creates
255 def inbox(conn, %{"type" => "Create"} = params) do
256 Logger.info(
257 "Signature missing or not from author, relayed Create message, fetching object from source"
258 )
259
260 Fetcher.fetch_object_from_id(params["object"]["id"])
261
262 json(conn, "ok")
263 end
264
265 def inbox(conn, params) do
266 headers = Enum.into(conn.req_headers, %{})
267
268 if String.contains?(headers["signature"], params["actor"]) do
269 Logger.info(
270 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
271 )
272
273 Logger.info(inspect(conn.req_headers))
274 end
275
276 json(conn, dgettext("errors", "error"))
277 end
278
279 defp represent_service_actor(%User{} = user, conn) do
280 with {:ok, user} <- User.ensure_keys_present(user) do
281 conn
282 |> put_resp_content_type("application/activity+json")
283 |> json(UserView.render("user.json", %{user: user}))
284 else
285 nil -> {:error, :not_found}
286 end
287 end
288
289 defp represent_service_actor(nil, _), do: {:error, :not_found}
290
291 def relay(conn, _params) do
292 Relay.get_actor()
293 |> represent_service_actor(conn)
294 end
295
296 def internal_fetch(conn, _params) do
297 InternalFetchActor.get_actor()
298 |> represent_service_actor(conn)
299 end
300
301 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
302 conn
303 |> put_resp_content_type("application/activity+json")
304 |> json(UserView.render("user.json", %{user: user}))
305 end
306
307 def whoami(_conn, _params), do: {:error, :not_found}
308
309 def read_inbox(
310 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
311 %{"nickname" => nickname, "page" => page?} = params
312 )
313 when page? in [true, "true"] do
314 with activities <-
315 (if params["max_id"] do
316 ActivityPub.fetch_activities([user.ap_id | user.following], %{
317 "max_id" => params["max_id"],
318 "limit" => 10
319 })
320 else
321 ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
322 end) do
323 conn
324 |> put_resp_content_type("application/activity+json")
325 |> put_view(UserView)
326 |> render("activity_collection_page.json", %{
327 activities: activities,
328 iri: "#{user.ap_id}/inbox"
329 })
330 end
331 end
332
333 def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
334 "nickname" => nickname
335 }) do
336 with {:ok, user} <- User.ensure_keys_present(user) do
337 conn
338 |> put_resp_content_type("application/activity+json")
339 |> put_view(UserView)
340 |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
341 end
342 end
343
344 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
345 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
346
347 conn
348 |> put_status(:forbidden)
349 |> json(err)
350 end
351
352 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
353 "nickname" => nickname
354 }) do
355 err =
356 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
357 nickname: nickname,
358 as_nickname: as_nickname
359 )
360
361 conn
362 |> put_status(:forbidden)
363 |> json(err)
364 end
365
366 def handle_user_activity(user, %{"type" => "Create"} = params) do
367 object =
368 params["object"]
369 |> Map.merge(Map.take(params, ["to", "cc"]))
370 |> Map.put("attributedTo", user.ap_id())
371 |> Transmogrifier.fix_object()
372
373 ActivityPub.create(%{
374 to: params["to"],
375 actor: user,
376 context: object["context"],
377 object: object,
378 additional: Map.take(params, ["cc"])
379 })
380 end
381
382 def handle_user_activity(user, %{"type" => "Delete"} = params) do
383 with %Object{} = object <- Object.normalize(params["object"]),
384 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
385 {:ok, delete} <- ActivityPub.delete(object) do
386 {:ok, delete}
387 else
388 _ -> {:error, dgettext("errors", "Can't delete object")}
389 end
390 end
391
392 def handle_user_activity(user, %{"type" => "Like"} = params) do
393 with %Object{} = object <- Object.normalize(params["object"]),
394 {:ok, activity, _object} <- ActivityPub.like(user, object) do
395 {:ok, activity}
396 else
397 _ -> {:error, dgettext("errors", "Can't like object")}
398 end
399 end
400
401 def handle_user_activity(_, _) do
402 {:error, dgettext("errors", "Unhandled activity type")}
403 end
404
405 def update_outbox(
406 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
407 %{"nickname" => nickname} = params
408 ) do
409 actor = user.ap_id()
410
411 params =
412 params
413 |> Map.drop(["id"])
414 |> Map.put("actor", actor)
415 |> Transmogrifier.fix_addressing()
416
417 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
418 conn
419 |> put_status(:created)
420 |> put_resp_header("location", activity.data["id"])
421 |> json(activity.data)
422 else
423 {:error, message} ->
424 conn
425 |> put_status(:bad_request)
426 |> json(message)
427 end
428 end
429
430 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
431 err =
432 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
433 nickname: nickname,
434 as_nickname: user.nickname
435 )
436
437 conn
438 |> put_status(:forbidden)
439 |> json(err)
440 end
441
442 def errors(conn, {:error, :not_found}) do
443 conn
444 |> put_status(:not_found)
445 |> json(dgettext("errors", "Not found"))
446 end
447
448 def errors(conn, _e) do
449 conn
450 |> put_status(:internal_server_error)
451 |> json(dgettext("errors", "error"))
452 end
453
454 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
455 with actor <- conn.params["actor"],
456 true <- is_binary(actor) do
457 Pleroma.Instances.set_reachable(actor)
458 end
459
460 conn
461 end
462
463 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
464 {:ok, new_user} = User.ensure_keys_present(user)
465
466 for_user =
467 if new_user != user and match?(%User{}, for_user) do
468 User.get_cached_by_nickname(for_user.nickname)
469 else
470 for_user
471 end
472
473 {new_user, for_user}
474 end
475 end