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