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