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