Merge remote-tracking branch 'remotes/upstream/develop' into 1234-mastodon-2-4-3...
[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(
34 Pleroma.Plugs.OAuthScopesPlug,
35 %{scopes: ["read:accounts"]} when action in [:followers, :following]
36 )
37
38 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
39 plug(:set_requester_reachable when action in [:inbox])
40 plug(:relay_active? when action in [:relay])
41
42 def relay_active?(conn, _) do
43 if Pleroma.Config.get([:instance, :allow_relay]) do
44 conn
45 else
46 conn
47 |> render_error(:not_found, "not found")
48 |> halt()
49 end
50 end
51
52 def user(conn, %{"nickname" => nickname}) do
53 with %User{} = user <- User.get_cached_by_nickname(nickname),
54 {:ok, user} <- User.ensure_keys_present(user) do
55 conn
56 |> put_resp_content_type("application/activity+json")
57 |> json(UserView.render("user.json", %{user: user}))
58 else
59 nil -> {:error, :not_found}
60 end
61 end
62
63 def object(conn, %{"uuid" => uuid}) do
64 with ap_id <- o_status_url(conn, :object, uuid),
65 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
66 {_, true} <- {:public?, Visibility.is_public?(object)} do
67 conn
68 |> assign(:tracking_fun_data, object.id)
69 |> set_cache_ttl_for(object)
70 |> put_resp_content_type("application/activity+json")
71 |> put_view(ObjectView)
72 |> render("object.json", object: object)
73 else
74 {:public?, false} ->
75 {:error, :not_found}
76 end
77 end
78
79 def track_object_fetch(conn, nil), do: conn
80
81 def track_object_fetch(conn, object_id) do
82 with %{assigns: %{user: %User{id: user_id}}} <- conn do
83 Delivery.create(object_id, user_id)
84 end
85
86 conn
87 end
88
89 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
90 with ap_id <- o_status_url(conn, :object, uuid),
91 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
92 {_, true} <- {:public?, Visibility.is_public?(object)},
93 likes <- Utils.get_object_likes(object) do
94 {page, _} = Integer.parse(page)
95
96 conn
97 |> put_resp_content_type("application/activity+json")
98 |> json(ObjectView.render("likes.json", ap_id, likes, page))
99 else
100 {:public?, false} ->
101 {:error, :not_found}
102 end
103 end
104
105 def object_likes(conn, %{"uuid" => uuid}) do
106 with ap_id <- o_status_url(conn, :object, uuid),
107 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
108 {_, true} <- {:public?, Visibility.is_public?(object)},
109 likes <- Utils.get_object_likes(object) do
110 conn
111 |> put_resp_content_type("application/activity+json")
112 |> json(ObjectView.render("likes.json", ap_id, likes))
113 else
114 {:public?, false} ->
115 {:error, :not_found}
116 end
117 end
118
119 def activity(conn, %{"uuid" => uuid}) do
120 with ap_id <- o_status_url(conn, :activity, uuid),
121 %Activity{} = activity <- Activity.normalize(ap_id),
122 {_, true} <- {:public?, Visibility.is_public?(activity)} do
123 conn
124 |> maybe_set_tracking_data(activity)
125 |> set_cache_ttl_for(activity)
126 |> put_resp_content_type("application/activity+json")
127 |> put_view(ObjectView)
128 |> render("object.json", object: activity)
129 else
130 {:public?, false} -> {:error, :not_found}
131 nil -> {:error, :not_found}
132 end
133 end
134
135 defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
136 object_id = Object.normalize(activity).id
137 assign(conn, :tracking_fun_data, object_id)
138 end
139
140 defp maybe_set_tracking_data(conn, _activity), do: conn
141
142 defp set_cache_ttl_for(conn, %Activity{object: object}) do
143 set_cache_ttl_for(conn, object)
144 end
145
146 defp set_cache_ttl_for(conn, entity) do
147 ttl =
148 case entity do
149 %Object{data: %{"type" => "Question"}} ->
150 Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
151
152 %Object{} ->
153 Pleroma.Config.get([:web_cache_ttl, :activity_pub])
154
155 _ ->
156 nil
157 end
158
159 assign(conn, :cache_ttl, ttl)
160 end
161
162 # GET /relay/following
163 def following(%{assigns: %{relay: true}} = conn, _params) do
164 conn
165 |> put_resp_content_type("application/activity+json")
166 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
167 end
168
169 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
170 with %User{} = user <- User.get_cached_by_nickname(nickname),
171 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
172 {:show_follows, true} <-
173 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
174 {page, _} = Integer.parse(page)
175
176 conn
177 |> put_resp_content_type("application/activity+json")
178 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
179 else
180 {:show_follows, _} ->
181 conn
182 |> put_resp_content_type("application/activity+json")
183 |> send_resp(403, "")
184 end
185 end
186
187 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
188 with %User{} = user <- User.get_cached_by_nickname(nickname),
189 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
190 conn
191 |> put_resp_content_type("application/activity+json")
192 |> json(UserView.render("following.json", %{user: user, for: for_user}))
193 end
194 end
195
196 # GET /relay/followers
197 def followers(%{assigns: %{relay: true}} = conn, _params) do
198 conn
199 |> put_resp_content_type("application/activity+json")
200 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
201 end
202
203 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
204 with %User{} = user <- User.get_cached_by_nickname(nickname),
205 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
206 {:show_followers, true} <-
207 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
208 {page, _} = Integer.parse(page)
209
210 conn
211 |> put_resp_content_type("application/activity+json")
212 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
213 else
214 {:show_followers, _} ->
215 conn
216 |> put_resp_content_type("application/activity+json")
217 |> send_resp(403, "")
218 end
219 end
220
221 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
222 with %User{} = user <- User.get_cached_by_nickname(nickname),
223 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
224 conn
225 |> put_resp_content_type("application/activity+json")
226 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
227 end
228 end
229
230 def outbox(conn, %{"nickname" => nickname} = params) 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 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
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} = params
312 ) do
313 conn
314 |> put_resp_content_type("application/activity+json")
315 |> put_view(UserView)
316 |> render("inbox.json", user: user, max_id: params["max_id"])
317 end
318
319 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
320 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
321
322 conn
323 |> put_status(:forbidden)
324 |> json(err)
325 end
326
327 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
328 "nickname" => nickname
329 }) do
330 err =
331 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
332 nickname: nickname,
333 as_nickname: as_nickname
334 )
335
336 conn
337 |> put_status(:forbidden)
338 |> json(err)
339 end
340
341 def handle_user_activity(user, %{"type" => "Create"} = params) do
342 object =
343 params["object"]
344 |> Map.merge(Map.take(params, ["to", "cc"]))
345 |> Map.put("attributedTo", user.ap_id())
346 |> Transmogrifier.fix_object()
347
348 ActivityPub.create(%{
349 to: params["to"],
350 actor: user,
351 context: object["context"],
352 object: object,
353 additional: Map.take(params, ["cc"])
354 })
355 end
356
357 def handle_user_activity(user, %{"type" => "Delete"} = params) do
358 with %Object{} = object <- Object.normalize(params["object"]),
359 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
360 {:ok, delete} <- ActivityPub.delete(object) do
361 {:ok, delete}
362 else
363 _ -> {:error, dgettext("errors", "Can't delete object")}
364 end
365 end
366
367 def handle_user_activity(user, %{"type" => "Like"} = params) do
368 with %Object{} = object <- Object.normalize(params["object"]),
369 {:ok, activity, _object} <- ActivityPub.like(user, object) do
370 {:ok, activity}
371 else
372 _ -> {:error, dgettext("errors", "Can't like object")}
373 end
374 end
375
376 def handle_user_activity(_, _) do
377 {:error, dgettext("errors", "Unhandled activity type")}
378 end
379
380 def update_outbox(
381 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
382 %{"nickname" => nickname} = params
383 ) do
384 actor = user.ap_id()
385
386 params =
387 params
388 |> Map.drop(["id"])
389 |> Map.put("actor", actor)
390 |> Transmogrifier.fix_addressing()
391
392 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
393 conn
394 |> put_status(:created)
395 |> put_resp_header("location", activity.data["id"])
396 |> json(activity.data)
397 else
398 {:error, message} ->
399 conn
400 |> put_status(:bad_request)
401 |> json(message)
402 end
403 end
404
405 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
406 err =
407 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
408 nickname: nickname,
409 as_nickname: user.nickname
410 )
411
412 conn
413 |> put_status(:forbidden)
414 |> json(err)
415 end
416
417 def errors(conn, {:error, :not_found}) do
418 conn
419 |> put_status(:not_found)
420 |> json(dgettext("errors", "Not found"))
421 end
422
423 def errors(conn, _e) do
424 conn
425 |> put_status(:internal_server_error)
426 |> json(dgettext("errors", "error"))
427 end
428
429 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
430 with actor <- conn.params["actor"],
431 true <- is_binary(actor) do
432 Pleroma.Instances.set_reachable(actor)
433 end
434
435 conn
436 end
437
438 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
439 {:ok, new_user} = User.ensure_keys_present(user)
440
441 for_user =
442 if new_user != user and match?(%User{}, for_user) do
443 User.get_cached_by_nickname(for_user.nickname)
444 else
445 for_user
446 end
447
448 {new_user, for_user}
449 end
450 end