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