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