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